Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions command.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
with Communicate(args.device, args.speed, timeout=args.timeout,
debug=args.debug,
quiet=args.quiet) as serial:

# now we send commands to the grbl, and wait waitTime for some response.
while True:
# Get some command
Expand All @@ -26,7 +26,7 @@
if x in ['~']: serial.run('~\n?')
# run it if is not a quit switch
serial.run(x)

except KeyboardInterrupt:
puts(colored.red('Emergency Feed Hold. Enter "~" to continue'))
serial.run('!\n?')
61 changes: 61 additions & 0 deletions draw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python
# draw.py : simulate mill/drill/etch to an EPS file
# [2015-04-17] - bkurtz
import os, re, sys
from math import ceil
from datetime import datetime
from lib.gcode import GCode
from lib.tool import Tool
from lib.drawing import Drawing
from lib import argv
from lib.util import deltaTime
from clint.textui import puts, colored


def main(etch_file, args=None):
start = datetime.now()
name = etch_file if isinstance(etch_file,str) else etch_file.name
puts(colored.blue('Visualizing the file: %s\n Started: %s'%(name,datetime.now())))

# Read in the gcode
gcode = GCode(etch_file, limit=None)
gcode.parse()

# parse the code into an array of tool moves
tool = Tool(gcode)
box = tool.boundBox()

# proces and save image
outfile = os.path.splitext(etch_file.name)[0] + '.eps'
print box
print box[0:2]
image = Drawing(outfile)#, bbox=box)
image.process(tool)
image.save()

# how long did this take?
puts(colored.green('Time to completion: %s'%(deltaTime(start))))
print


if __name__ == '__main__':
## I should wrap this in a __main__ section
# Initialize the args
start = datetime.now()
args = argv.arg(description='PyGRBL gcode imaging tool',
getFile=True, # get gcode to process
getMultiFiles=True, # accept any number of files
getDevice=False) # We dont need a device


# optimize each file in the list
for gfile in args.gcode:
# only process things not processed before.
# c = re.match(r'(?P<drill>\.drill\.tap)|(?P<etch>\.etch\.tap)', gfile.name)
c = re.match(r'(.+)(\.tap)', gfile.name)
# c = True # HAX and accept everything
if c: # either a drill.tap or etch.tap file
main(gfile, args=args)

print '%s finished in %s'%(args.name,deltaTime(start))

28 changes: 14 additions & 14 deletions lib/argv.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
from util import error


def arg(description=None, getDevice=True,
defaultSpeed=9600, defaultTimeout=0.70,
def arg(description=None, getDevice=True,
defaultSpeed=115200, defaultTimeout=0.70,
getFile=False, getMultiFiles=False,
otherOptions=None):
'''This is a simple arugment parsing function for all of the command line tools'''
if not description:
description='python grbl arguments'

parser = argparse.ArgumentParser(description=description)
parser.add_argument('-q','--quiet',
action='store_true',
Expand All @@ -25,16 +25,16 @@ def arg(description=None, getDevice=True,
action='store_true',
default=False,
help='[DEBUG] use a fake Serial port that prints to screen')

# by default just get the device
# HOWEVER if we want a file to be run, get it FIRST
if getFile:
nargs = '+' if getMultiFiles else 1
parser.add_argument('gcode',
parser.add_argument('gcode',
nargs=nargs,
type=argparse.FileType('r'),
type=argparse.FileType('r'),
help='gCode file to be read and processed.')

# if we want a device get an optional speed / and a device
if getDevice:
parser.add_argument('-s','--speed',
Expand All @@ -47,13 +47,13 @@ def arg(description=None, getDevice=True,
default=defaultTimeout,
type=float,
help='Serial Port Timeout: Amount of time to wait before gathering data for display [%.2f]'%(defaultTimeout))

parser.add_argument('device',
nargs='?',
# action='store_true',
default=False,
help='GRBL serial dev. Generally this should be automatically found for you. You should specify this if it fails, or your have multiple boards attached.')
help='GRBL serial dev. Generally this should be automatically found for you. You should specify this if it fails, or your have multiple boards attached.')

# For any specalized options lets have a general import method
if otherOptions:
if isinstance(otherOptions,(dict)):
Expand All @@ -66,12 +66,12 @@ def arg(description=None, getDevice=True,
parser.add_argument(*args, **option)

args = parser.parse_args()

# lets see if we can find a default device to connect too.
if args.debug: args.device='fakeSerial'
if (getFile) and (not getMultiFiles): args.gcode = args.gcode[0]
if getDevice and not args.device:
# Where they generally are:
# Where they generally are:
devs = ['/dev/tty.usb*','/dev/ttyACM*','/dev/tty.PL*','/dev/ttyUSB*']
founddevs = []
for d in devs:
Expand All @@ -83,8 +83,8 @@ def arg(description=None, getDevice=True,
else:
parser.print_help()
error('Found %d device(s) -- You need to connect a device, update %s, or specify wich device you want to use.'%(len(founddevs),sys.argv[0]))


args.name = sys.argv[0]
args.argv = sys.argv
return args
23 changes: 12 additions & 11 deletions lib/communicate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self, device, speed, debug=False, quiet=False, timeout=None):
# select the right serial device
if debug: s = FakeSerial()
else: s = serial.Serial(device, speed, timeout=timeout)

if not quiet: print '''Initializing grbl at device: %s
Please wait 1 second for device...'''%(device)
s.write("\r\n\r\n")
Expand All @@ -26,16 +26,17 @@ def __init__(self, device, speed, debug=False, quiet=False, timeout=None):
# self.run('$H')
# self.run('G20 (Inches)')
# self.run('G90 (Absolute)')


def run(self, cmd, singleLine=False):
'''Extends either serial device with a nice run command that prints out the
command and also gets what the device responds with.'''
puts(colored.blue(' Sending: [%s]'%cmd ), newline=(not singleLine))
out = '' # i think it is better to initialize out before sending the command
self.write(cmd+'\n')
out = ''
time.sleep(self.timeout)
# while s.inWaiting() > 0: out += s.read(10)
#Why is it not self.s.inWaiting() ????
while self.inWaiting() > 0: out += self.readline()
if out != '':
if singleLine:
Expand All @@ -44,27 +45,27 @@ def run(self, cmd, singleLine=False):
else:
puts(colored.green(''.join([' | '+o+'\n' for o in out.splitlines()])))
return out

def sendreset(self):
'''Sends crtl-x which is the reset key ::
\030 24 CAN \x18 ^X (Cancel)
'''
self.s.write(24)

def reset(self):
'''sends crtl-x to grbl to reset it -- clean up in the future'''
self.s.write(24)

def __enter__(self):
return self

def __exit__(self, type, value, traceback):
#self.s.setDTR(False)
#time.sleep(0.022)
#self.s.setDTR(True)
#self.s.close()
return isinstance(value, TypeError)

def __getattr__(self, name):
'''if no command here, see if it is in serial.'''
try:
Expand All @@ -76,14 +77,14 @@ def __getattr__(self, name):


class FakeSerial():
'''This is a fake serial device that mimics a true serial devie and
'''This is a fake serial device that mimics a true serial devie and
does fake read/write.'''
def __init__(self):
'''init the fake serial and print out ok'''
self.waiting=1 # If we are waiting
self.ichar=0 # index of the character that we are on.
self.msg='ok' # the message that we print when we get any command

def __getattr__(self, name):
print 'DEBUG SERIAL: %s'%(name)
return self.p
Expand Down Expand Up @@ -115,4 +116,4 @@ def inWaiting(self):
out = self.waiting
if self.waiting == 0:
self.waiting = 1
return out
return out
129 changes: 129 additions & 0 deletions lib/correction_surface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env python

#this code should use a 2 dimensional array of z_positions
#the element 0,0 is 0 and all other elements are relative z positions
#there should also be a probe_step_x value and a probe_step_y value
#these define the distsnces between the points measured in the z_positions array


#imports
from numpy import arange
from lib import argv
from lib.gparse import gparse
from lib.grbl_status import GRBL_status
from lib.communicate import Communicate
from clint.textui import puts, colored
import time, readline
import numpy as np
import re


class CorrectionSurface():
def __init__(self,surface_file_name = ''):
self.x_step = "nan"
self.y_step = "nan"
self.array = []
if surface_file_name != '':
#print 'this is the surface correcion name that init is loading'
#print surface_file_name
self.Load_Correction_Surface(surface_file_name)
else:
self.Load_Correction_Surface()

def Load_Correction_Surface(self, surface_file_name = 'probe_test.out' ):

#initialize and load the correction surface
Surface_Data = []
Surface_Data = np.loadtxt(surface_file_name, delimiter=',', comments = '#')
#correction_surface.array = Surface_Data
self.array = Surface_Data

#display the dataset to be used
puts(colored.yellow('Surface Z Data Matrix'))
puts(colored.yellow(np.array_str(Surface_Data)))
#print Surface_Data

probe_data_file = open(surface_file_name)

#probe_data = probe_data_file.read()

# retrieve the X and Y step sizes that scale the correction surface
for line in probe_data_file:
line = line.strip()

if re.search('#', line,flags = re.IGNORECASE):
#puts(colored.green('extracting scale data from file header'))
#puts(colored.green( line))
if re.search(' X_STEP:', line,flags = re.IGNORECASE):
#X_STEP:,0.5000
X_STEP_INFO = re.findall('X_STEP:,\d*\.\d*,', line, flags = re.IGNORECASE)[0]
if X_STEP_INFO:
X_STEP = float(X_STEP_INFO.split(',')[1])
puts(colored.yellow( 'x step size: {:.4f}'.format(X_STEP)))
#correction_surface.x_step = X_STEP
self.x_step = X_STEP
else:
puts(colored.red( 'x step size: not found!'))
if re.search(' Y_STEP:', line,flags = re.IGNORECASE):
#X_STEP:,0.5000
Y_STEP_INFO = re.findall('Y_STEP:,\d*\.\d*,', line, flags = re.IGNORECASE)[0]
if Y_STEP_INFO:
Y_STEP = float(Y_STEP_INFO.split(',')[1])
puts(colored.yellow( 'Y step size: {:.4f}'.format(Y_STEP)))
#correction_surface.Y_step = Y_STEP
self.y_step = Y_STEP
else:
puts(colored.red( 'Y step size: not found!'))
return self

def estimate_surface_z_at_pozition(self,x,y):
#find the bounding triangle on the correction surface closest to the x,y coordinet
#begin with using the nearest node to the point
Ns_x = round(x/self.x_step)
Ns_y = round(y/self.y_step)
# calcualte the boundaries
Nmax_x = self.array.shape[0]
Nmax_y = self.array.shape[1]
#calculate distance from the nearest node
epsilon_x= x - Ns_x*self.x_step
epsilon_y= y - Ns_y*self.x_step
#find the next two nearest nodes
if (x > Ns_x*self.x_step):
Nf_x = Ns_x + 1
delta_x = self.x_step
else:
Nf_x = Ns_x + -1
delta_x = -1.0*self.x_step
if (y > Ns_y*self.x_step):
Nf_y = Ns_y + 1
delta_y = self.y_step
else:
Nf_y = Ns_y + -1
delta_y = -1.0*self.y_step

#force the data into the boundaries
if Ns_x<0:
Ns_x = 0
if Ns_y<0:
Ns_y = 0
if Nf_x<0:
Nf_x = 0
if Nf_y<0:
Nf_y = 0

if Ns_x>Nmax_x:
Ns_x = Nmax_x
if Ns_y>Nmax_y:
Ns_y = Nmax_y
if Nf_x>Nmax_x:
Nf_x = Nmax_x
if Nf_y>Nmax_y:
Nf_y = Nmax_y

#calculate the slopes (x and y) of the plane defined by the triangle of bounding nodes
slope_x = (self.array[Nf_x][Ns_y] - self.array[Ns_x][Ns_y])/delta_x
slope_y = (self.array[Ns_x][Nf_y] - self.array[Ns_x][Ns_y])/delta_y
#use the slope information and the origin intercept at the clsest node
# to estimate the z position of the surface
z_at_position = self.array[Ns_x][Ns_y] + epsilon_x*slope_x + epsilon_y*slope_y
return z_at_position
Loading