Skip to content

Instantly share code, notes, and snippets.

@thegaragelab
Created January 28, 2015 08:16
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save thegaragelab/eb23dab5eae710674486 to your computer and use it in GitHub Desktop.
Scripts and tools for measuring axis movement using LinuxCNC.
#!/usr/bin/env python
#----------------------------------------------------------------------------
# 11-Jan-2015 ShaneG
#
# Turn the results of the autoprobe log into a PNG file for visualisation.
#----------------------------------------------------------------------------
from sys import argv
from os.path import splitext
from random import random
from PIL import Image
HEIGHT_MIN = None
HEIGHT_MAX = None
STEP_X = None
STEP_Y = None
VALUES = list()
SPREAD = 1.6
def makeValues(xsteps, ysteps):
""" Generate a set of random values for testing
"""
global HEIGHT_MIN, HEIGHT_MAX, STEP_X, STEP_Y, VALUES
for i in range(xsteps * ysteps):
z = (random() * 3.2) - 1.6
# Check min and max height first
if (HEIGHT_MIN is None) or (z < HEIGHT_MIN):
HEIGHT_MIN = z
if (HEIGHT_MAX is None) or (z > HEIGHT_MAX):
HEIGHT_MAX = z
# Add to the list
VALUES.append(z)
# Set steps
STEP_X = xsteps
STEP_Y = ysteps
def loadValues(filename):
""" Load the values from the log probe into an array, calculate the min and
max heights found as well as the number of X and Y steps.
"""
global VALUES, STEP_X, STEP_Y
xvals = dict()
yvals = dict()
results = dict()
# Helper to add a single value to the dictionary
def addValue(x, y, z):
global HEIGHT_MIN, HEIGHT_MAX
# Check min and max height first
if (HEIGHT_MIN is None) or (z < HEIGHT_MIN):
HEIGHT_MIN = z
if (HEIGHT_MAX is None) or (z > HEIGHT_MAX):
HEIGHT_MAX = z
# Update X and Y vals
if not xvals.has_key(x):
xvals[x] = 1
else:
xvals[x] = xvals[x] + 1
if not yvals.has_key(y):
yvals[y] = 1
else:
yvals[y] = yvals[y] + 1
# Flesh out the dictionary
if not results.has_key(y):
results[y] = dict()
results[y][x] = z
# Process the input file
with open(filename, "r") as input:
for line in input:
position = [ round(25.4 * float(x), 4) for x in line.split(" ")[:3] ]
addValue(*position)
# Get the number of x and y values
STEP_X = len(xvals)
STEP_Y = len(yvals)
# Get the center point so we have a comparable reference
global HEIGHT_MIN, HEIGHT_MAX
mid = HEIGHT_MIN + (HEIGHT_MAX - HEIGHT_MIN) / 2
# Generate a csv file
with open(splitext(filename)[0] + ".csv", "w") as output:
output.write("," + ",".join([ "%0.4f" % x for x in sorted(xvals.keys()) ]) + "\n")
for y in sorted(yvals.keys(), reverse = True):
output.write("%0.4f,%s\n" % (y, ",".join([ "%0.4f" % results[y][x] for x in sorted(xvals.keys()) ])))
# Turn it into an array
for y in sorted(yvals.keys(), reverse = True):
for x in sorted(xvals.keys()):
if not results.has_key(y):
print "Error: Missing expected value for %0.4f,%0.4f" % (x, y)
exit(1)
if not results[y].has_key(x):
print "Error: Missing expected value for %0.4f,%0.4f" % (x, y)
exit(1)
VALUES.append(results[y][x] - mid)
# Update min and max height
HEIGHT_MIN = min(VALUES)
HEIGHT_MAX = max(VALUES)
def getColor(v):
global SPREAD
c = (0, 0, 0)
if v < 0.0:
c = (0, 0, int(abs(v / SPREAD * 255)))
else:
c = (0, int(v / SPREAD * 255), 0)
return c
def avg(*args):
return sum(args) / (1.0 * len(args))
def expandVals(width, height, values):
results = list()
# Get a particular value
def getValue(xc, yc):
return values[yc * width + xc]
# Walk through interpolating the middle points
for y in range(height - 1):
# New even line - Interpolate center points only
results.append(getValue(0, y))
for x in range(1, width):
results.append(avg(getValue(x - 1, y), getValue(x, y)))
results.append(getValue(x, y))
# New odd lines - interpolate above, below and center
for x in range(width - 1):
results.append(avg(getValue(x, y), getValue(x, y + 1)))
results.append(avg(getValue(x, y), getValue(x + 1, y), getValue(x, y + 1), getValue(x + 1, y + 1)))
results.append(avg(getValue(width - 1, y), getValue(width - 1, y + 1)))
# And add the last line
results.append(getValue(0, height - 1))
for x in range(1, width):
results.append(avg(getValue(x - 1, height - 1), getValue(x, height - 1)))
results.append(getValue(x, height - 1))
# All done
return 2 * width - 1, 2 * height - 1, results
def generateImage(filename):
""" Generate a height map image
"""
global STEP_X, STEP_Y, VALUES
# Interpolate values
width = STEP_X
height = STEP_Y
points = VALUES
for i in range(5):
width, height, points = expandVals(width, height, points)
# Create the image
img = Image.new("RGB", (width, height), (0, 0, 0))
for y in range(height):
for x in range(width):
img.putpixel((x, y), getColor(points[y * width + x]))
# Save the file
img.save(filename)
if __name__ == "__main__":
# Check arguments
if len(argv) != 2:
print "Usage:"
print " %s <filename>" % argv[0]
exit(1)
# Load the values
loadValues(argv[1])
# makeValues(16, 10)
# Show some stats and check the heights
print "Min height: %0.4f" % HEIGHT_MIN
print "Max height: %0.4f" % HEIGHT_MAX
print "%d unique X values, %d unique y values" % (STEP_X, STEP_Y)
if HEIGHT_MIN > SPREAD or HEIGHT_MIN < -SPREAD or HEIGHT_MAX > SPREAD or HEIGHT_MAX < -SPREAD:
print "Error: Cannot generate image, height variation is too large"
else:
generateImage(splitext(argv[1])[0] + ".jpg")
#!/usr/bin/env python
#----------------------------------------------------------------------------
# 16-Jan-2015 ShaneG
#
# Generate a probe file for a given area.
#----------------------------------------------------------------------------
from sys import argv
from optparse import OptionParser
from random import shuffle
#--- Usage information
USAGE = """
Usage:
%s [--width width] [--height height] [--xstep xstep] [--ystep ystep] [--safe safe_height] [--maxmove maxmove] filename
"""
# Prefix code (use safe)
GCODE_PREFIX = """
G20 (Use inch)
G90 (Set Absolute Coordinates)
(begin initial probe and set Z to 0)
G0 Z%0.4f(Move clear of the board first)
"""
# First probe - use pos, safe, maxmove, safe, maxmove, safe
GCODE_FIRST = """
G0 X%0.4f Y%0.4f
G0 Z%0.4f(Quick move to probe clearance height)
G38.2 Z%0.4f F5
G10 L20 P0 Z0
G0 Z%0.4f
G38.2 Z%0.4f F2.5
G10 L20 P0 Z0
(PROBEOPEN RawProbeLog.txt)
G0 Z%0.4f
"""
# Single probe step - use x, y, maxmove, feedrate, safe
GCODE_STEP = """
G0 X%0.4f Y%0.4f
G38.2 Z%0.4f F%0.4f
G0 Z%0.4f
"""
# Suffix code - use safe
GCODE_SUFFIX = """
(Operation complete)
G0 X0 Y0 Z%0.4f
(PROBECLOSE)
M2
"""
#--- Main program
if __name__ == "__main__":
# Set up program options
parser = OptionParser()
parser.add_option("-w", "--width", action="store", type="float", dest="width")
parser.add_option("-H", "--height", action="store", type="float", dest="height")
parser.add_option("-x", "--xstep", action="store", type="int", dest="xstep", default=10)
parser.add_option("-y", "--ystep", action="store", type="int", dest="ystep", default=10)
parser.add_option("-m", "--maxmove", action="store", type="float", dest="maxmove", default=-0.5)
parser.add_option("-s", "--safe", action="store", type="float", dest="safe", default=5.0)
parser.add_option("-r", "--random", action="store_true", dest="random", default=False)
parser.add_option("-b", "--backwards", action="store_true", dest="backwards", default=False)
parser.add_option("-f", "--feedrate", action="store", type="float", dest="feedrate", default=127)
options, args = parser.parse_args()
# Make sure required arguments are present
for req in ("width", "height"):
if eval("options.%s" % req) is None:
print "ERROR: Missing required argument '%s'" % req
print USAGE.strip() % argv[0]
exit(1)
# Check positional arguments
if len(args) <> 1:
print USAGE.strip() % argv[0]
exit(1)
# Show current settings
print "Selected options:"
for opt in parser.option_list:
if opt.dest is not None:
print " %s = %s" % (opt.dest, str(eval("options.%s" % opt.dest)))
# Generate the list of probe points
dx = options.width / options.xstep
dy = options.height / options.ystep
points = list()
for y in range(options.ystep + 1):
if (y % 2) == 0:
# Even lines
for x in range(options.xstep + 1):
points.append((x * dx / 25.4, y * dy / 25.4, options.maxmove / 25.4))
else:
# Odd lines
for x in range(options.xstep, -1, -1):
points.append((x * dx / 25.4, y * dy / 25.4, options.maxmove / 25.4))
# Reverse order if needed
if options.backwards:
points = list(reversed(points))
# Shuffle if needed
if options.random:
shuffle(points)
# Start generating the file
print "Creating file '%s'" % args[0]
output = open(args[0], "w")
output.write(GCODE_PREFIX.strip() % (options.safe / 25.4) + "\n")
# Do the first move
output.write(GCODE_FIRST.strip() % (points[0][0], points[0][1], options.safe / 25.4, options.maxmove / 25.4, options.safe / 25.4, options.maxmove / 25.4, options.safe / 25.4) + "\n")
# Do the individual probes
for p in points:
output.write(GCODE_STEP.strip() % (p[0], p[1], options.maxmove / 25.4, options.feedrate / 25.4, options.safe / 25.4) + "\n")
# Generate the suffix
output.write(GCODE_SUFFIX.strip() % (options.safe / 25.4) + "\n")
output.close()
G20 (Use Inches)
G91 (Set Relative Coordinates)
G17 (XY plane selection)
(Variables)
#500=1.2599 (Feed rate)
#501=3 (Distance between points)
#502=30 (Number of probes in a sequence)
(Subroutine for probes)
o100 sub
#100=0
o101 while [#100 LT #502]
G38.2 X#1 Y#2 F#3
G28
#100 = [#100+1] (increment the test counter)
o101 endwhile
o100 endsub
(Save the current position)
G28.1
(PROBEOPEN RawProbeLog.txt)
(Probe in positive Y direction)
o100 call [0] [#501 / 2] [#500]
(Probe in positive X direction)
o100 call [#501 / 2] [0] [#500]
(Probe in negative Y direction)
o100 call [0] [-#501 / 2] [#500]
(Probe in negative X direction)
o100 call [-#501 / 2] [0] [#500]
(PROBECLOSE)
M02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment