Scripts and tools for measuring axis movement using LinuxCNC.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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