cyclone-pcb-factory/Software/Previous/Etch_Z_adjust.1.8.py

604 lines
24 KiB
Python
Raw Normal View History

#!/usr/bin/python
# This code is from http://www.cnczone.com/forums/pcb_milling/82628-cheap_simple_height-probing.html
# sudo apt-get install python-tk
## Introduction
# This code when run will ask you:
# (1) what units your file uses (inch or mm)
# (2) how many steps you want your probe gride to have on the X axis
# (3) how many steps you want your probe gride to have on the Y axis
# (4) which PCB G code file you want to etch
# It then gets your mill to probe a grid on a blank PCB based on your spacing
# It stores the probe Z values to memory, then uses those Z values to adjust Z heights for etch moves along the now well probed PCB.
# You can reuse the generated file again and again as it will always start out by re-probing the surface before etching.
# It is based on a neat idea by Poul-Henning Kamp. See: http://phk.freebsd.dk/CncPcb/index.html
## Where the output is saved
# The output is saved to a new file where
# file_out_name = file_in_name + file_name_suffix,
# and the default file out suffix (which you could change by modifying the code) is "_Zadj_[grid size].ngc"
# Finally, and this is very handy if you are running the Python code in EMC2, it outputs the file to the screen, and you are ready to roll.
# You can always comment out the print line if you don't need it.
## What it doesn't do
# It doesn't reject spurious probe values - you need to make sure the PCB blank you use is clean and free of debris
# It doesn't optimise X Y paths (you could use opti_qt.exe to do this first - see http://pcbgcode.org/read.php?6,5,5))
# It doesn't optimise X Y moves between drills (Opti_qt can do this for 'simple drill moves', Gopt is apparently better for multiple drill sizes)
# It doesn't adjust Z heights during arc etching (G02 and G03 moves) - this should not be a problem if arcs have a diameter of less than 10mm.
# Feel free to address any of these issues!
## How it works
# This Python code parses your selected G code file and looks at every etch move: G01 Xaa Ybb Zcc Fdd, where Z is greater than -0.5
# It ignores milling and drilling moves ie where Z is deeper than -0.5mm.
# It finds the max and min values of X and Y from amongst the file's etch moves.
# It then generates a custom G code routine that will probe a grid that encompasses those max and min X and Y values.
# The probe points are spaced by the selected grid spacing, and the Z values at each probe point are stored in memory.
# It generates a G code subroutine that will draw on the stored probe data and does the etching on the PCB at the adjusted heights.
# For long etch moves it puts in a way point at a distance of half the X grid spacing and calculates a new etch depth for each way point
# as it goes along.
# All the etch moves in the original file(ie G01 Xaa Ybb Zcc Fdd (where Z is greater than -0.5) are then replaced by a subroutine call
# in the format O200 sub [x_start] [y_start] [aa] [bb] (where O200 is the etch subroutine referred to above)
# You can change the defaults a little further down
## For ease of use you may want to change the default start up directory here:
# UNITLESS DEFAULTS: These values are not unit sensitive (and you can change these here too)
# Remember in EMC you can probe up to 4000 points, in Mach3 up to 1000
initial_directory = '/home/'
X_grid_lines = 10
Y_grid_lines = 5
units = "mm"
grid_def = "step size"
file_in_name = initial_directory
def Unit_set():
global units,units_G_code,G_dest, X_dest,Y_dest,Z_dest,etch_definition,etch_speed,probe_speed,z_safety,z_probe
global etch_depth,etch_max,etch_min,z_trivial,z_probe_detach,grid_clearance,step_size,step_max,step_min
global X_grid_lines,Y_grid_lines,grid_max,grid_min,grid_def
# INCH DEFAULTS: if units are inches, set the defaults in inches (you can change these here)
if units == "inch":
units_G_code = 20
G_dest = '00'
X_dest = -3.000
Y_dest = 2.000
Z_dest = 2.000
etch_definition = -0.020
etch_speed = 4.000
probe_speed = 0.400
z_safety = 0.150
z_probe = -0.150
etch_depth = 0.004
etch_max = 0.020
z_trivial = 0.001
z_probe_detach = 2.000
grid_clearance = 0.001
step_size = 0.400
# MM DEFAULTS: if units are mm, set the defaults in mm (you can change these here too)
elif units == "mm":
units_G_code = 21
G_dest = '00'
X_dest = -80.00
Y_dest = 40.00
Z_dest = 40.00
etch_definition = -0.50
etch_speed = 120.00
probe_speed = 25.00
z_safety = 1.00
z_probe = -1.00
etch_depth = 0.10
etch_max = 0.50
z_trivial = 0.02
z_probe_detach = 40.00
grid_clearance = 0.01
step_size = 10.00
def Unit_sel():
global units, G_dest, X_dest,Y_dest,Z_dest,etch_definition,etch_speed,probe_speed,z_safety,z_probe
global etch_depth,etch_max,etch_min,z_trivial,z_probe_detach,grid_clearance,step_size,step_max,step_min
global X_grid_lines,Y_grid_lines,grid_max,grid_min,grid_def, file_in_name
units = get_units.get()
Unit_set()
# Refresh the defaults in the display
RB_step.config(text = "grid step size (" + units + ")")
L_etch_depth.config(text = "Etch depth (" + units + "):")
Ent_etch_depth.delete(0, END)
Ent_etch_depth.insert(0, etch_depth)
Ent_step.config(state=NORMAL)
Ent_step.delete(0, END)
Ent_step.insert(0, step_size)
if get_grid_def.get() == "grid lines":
Ent_step.config(state=DISABLED)
def Def_sel():
grid_def = get_grid_def.get()
if grid_def == "step size":
Ent_X.config(state=DISABLED)
Ent_Y.config(state=DISABLED)
Ent_step.config(state=NORMAL)
elif grid_def == "grid lines":
Ent_X.config(state=NORMAL)
Ent_Y.config(state=NORMAL)
Ent_step.config(state=DISABLED)
Ent_X.delete(0, END)
Ent_X.insert(0, X_grid_lines)
Ent_Y.delete(0, END)
Ent_Y.insert(0, Y_grid_lines)
def Browse():
global file_in_name
import tkFileDialog
file_in_name = tkFileDialog.askopenfilename(parent=top,initialdir=initial_directory,
filetypes= [('nc files', '*.ngc'),('nc files', '*.nc')],title='Choose file to import:')
L_file_in_name.config(text = file_in_name)
def OK() : top.destroy()
# Entry validation functions
def IntCheck(new_string) :
if new_string == "": new_string = "0"
try:
v = int(new_string)
if v > 99 or v < 0: return False
return True
# True means accept the new string
except ValueError:
return False
# False means don't accept it
def EtchCheck(new_string) :
global etch_max
if new_string == "": new_string = "0"
try:
v = float(new_string)
if v > etch_max or v < 0: return False
if len(new_string) > 6 : return False
return True
except ValueError:
return False
# False means don't accept it
def StepCheck(new_string) :
if new_string == "": new_string = "0"
try:
v = float(new_string)
if v < 0: return False
if len(new_string) > 6 : return False
return True
# True means accept the new string
except ValueError:
return False
# False means don't accept it
def get_num(line,char_ptr,num_chars):
char_ptr=char_ptr+1
numstr = ''
good = '-.0123456789'
while char_ptr < num_chars:
digit = line[char_ptr]
if good.find(digit) != -1:
numstr = numstr + digit
char_ptr = char_ptr + 1
else: break
return numstr
def test_X(X_min, X_max):
if X_dest < X_min : X_min = X_dest
elif X_dest > X_max : X_max = X_dest
return X_min, X_max
def test_Y(Y_min, Y_max):
if Y_dest < Y_min : Y_min = Y_dest
elif Y_dest > Y_max : Y_max = Y_dest
return Y_min, Y_max
from Tkinter import *
## Don't change these ...
file_in = []
file_out = []
intro = []
numstr = ''
char = ''
Unit_set()
top = Tk()
top.title("Etch_Z_adjust setup")
# Define the Tkinter variables
get_units = StringVar()
get_grid_def = StringVar()
get_X = IntVar()
get_Y = IntVar()
get_step = DoubleVar()
get_etch = DoubleVar()
get_file_in = StringVar()
# define the label, radiobutton, button and entry widgets:
# Label widgets:
L_blank1 = Label(top, text="")
L_blank2 = Label(top, text="")
L_blank3 = Label(top, text="")
L_blank4 = Label(top, text="")
L_blank5 = Label(top, text="")
L_units = Label(top, text="Units to use:")
L_grid_def = Label(top, text="Define grid by:")
L_X_eq = Label(top, text="X = ")
L_Y_eq = Label(top, text="Y = ")
L_etch_depth = Label(top, text="Etch depth:")
L_file_in_quest = Label(top, text="File to import:")
L_file_in_name = Label(top, text="")
# Radiobutton widgets:
RB_inch = Radiobutton(top, padx=45, text="inch", variable=get_units, value="inch", command=Unit_sel)
RB_mm = Radiobutton(top, padx=45, text="millimetre", variable=get_units, value="mm", command=Unit_sel)
RB_lines = Radiobutton(top, padx=45, text="number of grid lines", variable=get_grid_def, value="grid lines", command=Def_sel)
RB_step = Radiobutton(top, padx=45, text="grid step size", variable=get_grid_def, value="step size", command=Def_sel)
# Button widgets:
B_browse = Button(top, text ="Browse...", command = Browse)
B_cancel = Button(top, text ="CANCEL", command = OK)
B_OK = Button(top, text ="OK", command = OK)
# Entry widgets with ***validation***
# top.register(func_name) gives a number that when called by validatecommand as a command
# enables validatecommand to pass Tk % parameters to the function called eg func_name.
# ' %P' (the space is important) is the string value in the Entry widget that would result if the edit was allowed.
# See http://www.tcl.tk/man/tcl8.4/TkCmd/entry.htm#M16 for a list of these % parameters.
val_int = top.register(IntCheck)
val_etch = top.register(EtchCheck)
val_step = top.register(StepCheck)
Ent_X = Entry(master=top, width = 2, textvariable = get_X,
validate = "key", validatecommand = val_int + ' %P')
Ent_Y = Entry(master=top, width = 2, textvariable = get_Y,
validate = "key", validatecommand = val_int + ' %P')
Ent_step = Entry(master=top, width = 6, textvariable = get_step,
validate = "key", validatecommand = val_step + ' %P')
Ent_etch_depth = Entry(master=top, width = 6, textvariable = get_etch,
validate = "key", validatecommand = val_etch + ' %P ')
Ent_file_in = Entry(master=top, width = 70, textvariable = get_file_in, justify = RIGHT, state = DISABLED)
# lay out the widgets:
L_file_in_quest.grid(row=0, column=0)
B_browse.grid (row=0, column=1)
L_file_in_name.grid (row=1, column=0, columnspan = 7)
L_blank5.grid (row=2, column=0, columnspan = 7)
L_units.grid (row=3, column=0, sticky=W, padx = 25)
RB_mm.grid (row=4, column=0, sticky=W)
RB_inch.grid (row=5, column=0, sticky=W)
L_blank2.grid (row=6, column=0)
L_etch_depth.grid (row=7, column=0, sticky=W, padx = 25)
Ent_etch_depth.grid (row=7, column=1, sticky=W)
L_blank3.grid (row=8, column=1)
L_grid_def.grid (row=3, column=1, sticky=W, padx = 25)
RB_step.grid (row=4, column=1, sticky=W)
Ent_step.grid (row=4, column=2, sticky=W)
RB_lines.grid (row=5, column=1, sticky=W)
L_X_eq.grid (row=6, column=1, sticky=E)
Ent_X.grid (row=6, column=2, sticky=W)
L_Y_eq.grid (row=7, column=1, sticky=E)
Ent_Y.grid (row=7, column=2, sticky=W)
B_cancel.grid (row=11, column=0)
B_OK.grid (row=11, column=1)
L_blank4.grid (row=12, column=1)
# set the initial units to mm
RB_mm.select()
Unit_sel()
# set the initial grid definition to step size
RB_step.select()
Def_sel()
top.mainloop()
units = get_units.get()
grid_def = get_grid_def.get()
step_size = get_step.get()
etch_depth = - get_etch.get()
X_grid_lines = get_X.get()
Y_grid_lines = get_Y.get()
# read in G code file
if file_in_name != None:
f = open(file_in_name, 'r')
for line in f:
file_in.append(line)
f.close()
# Check for max and min values in your ngc file
is_first_X = True
is_first_Y = True
is_first_Z = True
# check each line
line_ptr=0
num_lines=len(file_in)
while line_ptr < num_lines:
line = file_in[line_ptr]
X_start = X_dest
Y_start = Y_dest
Z_start = Z_dest
# check each character
char_ptr = 0
num_chars= len(line)
while char_ptr < num_chars:
char = line[char_ptr]
if '(;'.find(char) != -1:
break
elif char == 'G' :
G_dest = get_num(line,char_ptr,num_chars)
elif char == 'X' :
X_dest = float(get_num(line,char_ptr,num_chars))
elif char == 'Y' :
Y_dest = float(get_num(line,char_ptr,num_chars))
elif char == 'Z' :
Z_dest = float(get_num(line,char_ptr,num_chars))
char_ptr = char_ptr + 1
# if the line is an etch move, then replace the line with an etch call
if G_dest == '01' and Z_dest > etch_definition:
line = 'O200 call [%.4f] [%.4f] [%.4f] [%.4f]\n' % (X_start, Y_start, X_dest, Y_dest)
# and now check for max and min X and Y values
if is_first_X == True :
X_min = X_dest
X_max = X_dest
is_first_X = False
else : (X_min, X_max) = test_X(X_min, X_max)
if is_first_Y == True :
Y_min = Y_dest
Y_max = Y_dest
is_first_Y = False
else : (Y_min, Y_max) = test_Y(Y_min, Y_max)
file_out.append(line)
line_ptr=line_ptr+1
if is_first_X == False :
# then there were etch moves so get to work!
# first stretch the X and Y max and min values a _tiny_ amount so the grid is just outside all the etch points
X_min = X_min - grid_clearance
X_max = X_max + grid_clearance
Y_min = Y_min - grid_clearance
Y_max = Y_max + grid_clearance
# Use max and min values for the etch moves to work out the probe grid dimensions
X_span = X_max - X_min
X_grid_origin = X_min
Y_span = Y_max - Y_min
Y_grid_origin = Y_min
# Now work out the X and Y step sizes
if grid_def == "step size" :
X_grid_lines = 2 + int(X_span/step_size)
Y_grid_lines = 2 + int(Y_span/step_size)
# Make sure grid lines are at least 2
if X_grid_lines < 2 : X_grid_lines = 2
if Y_grid_lines < 2 : Y_grid_lines = 2
Y_step_size = Y_span / (Y_grid_lines - 1)
X_step_size = X_span / (X_grid_lines - 1)
# Now we can name the output file
file_name_suffix = "_Zadj_%dx%d.ngc" % (X_grid_lines, Y_grid_lines)
n = file_in_name.rfind(".")
if n != -1:
file_out_name = file_in_name[0:n] + file_name_suffix
else: file_out_name = file_in_name + file_name_suffix
# OK now output the G code intro (probe subroutine + etch subroutine + customised grid code)
from time import localtime, strftime
line = "(imported from " + file_in_name + " at " + strftime("%I:%M %p on %d %b %Y", localtime())+ ")\n"
intro.append(line)
line = "(output saved as " + file_out_name + ")\n\n"
intro.append(line)
line = "(G code configuration section)\n(you can change these values in the python code or in the G code output:)\n"
intro.append(line)
line = ("G%2d (" + units + ")\n") % (units_G_code)
intro.append(line)
line = "#<_etch_speed> = %.4f \n" % (etch_speed)
intro.append(line)
line = "#<_probe_speed> = %.4f \n" % (probe_speed)
intro.append(line)
line = "#<_z_safety> = %.4f \n" % (z_safety)
intro.append(line)
line = "#<_z_probe> = %.4f \n" % (z_probe)
intro.append(line)
line = "#<_etch_depth> = %.4f \n\n" % (etch_depth)
intro.append(line)
line = "(Don't change these values here, they were calculated earlier)\n"
intro.append(line)
line = '#<_x_grid_origin> = %.4f \n' % (X_grid_origin)
intro.append(line )
line = '#<_x_grid_lines> = %.4f \n' % (X_grid_lines )
intro.append(line)
line = '#<_y_grid_origin> = %.4f \n' % (Y_grid_origin)
intro.append(line)
line = '#<_y_grid_lines> = %.4f \n' % (Y_grid_lines )
intro.append(line)
line = '#<_x_step_size> = %.4f \n' % (X_step_size)
intro.append(line)
line = '#<_y_step_size> = %.4f \n' % (Y_step_size)
intro.append(line)
line = """#<_last_z_etch> = #<_etch_depth>
O100 sub (probe subroutine)
G00 X [#<_x_grid_origin> + #<_x_step_size>*#<_grid_x>]
G38.2 Z#<_z_probe> F#<_probe_speed>
#[1000 + #<_grid_x> + #<_grid_y> * #<_x_grid_lines>] = #5063
G00 Z#<_z_safety>
O100 endsub
O200 sub (etch subroutine)
( This subroutine calculates way points on the way to x_dest, y_dest, )
( and calculates the Z adjustment at each way point. )
( It moves to each way point using the etch level and etch speed set )
( in the configuration section above. )
#<x_start> = #1
#<y_start> = #2
#<x_dest> = #3
#<y_dest> = #4
#<distance> = sqrt[ [#<x_dest> - #<x_start>]**2 + [#<y_dest> - #<y_start>]**2 ]
#<waypoint_number> = fix[#<distance> / [#<_x_step_size>/2]]
#<x_step> = [[#<x_dest> - #<x_start>] / [#<waypoint_number> + 1]]
#<y_step> = [[#<y_dest> - #<y_start>] / [#<waypoint_number> + 1]]
O201 while [#<waypoint_number> ge 0]
#<_x_way> = [#<x_dest> - #<waypoint_number> * #<x_step>]
#<_y_way> = [#<y_dest> - #<waypoint_number> * #<y_step>]
#<_grid_x_w> = [[#<_x_way> - #<_x_grid_origin>]/#<_x_step_size>]
#<_grid_y_w> = [[#<_y_way> - #<_y_grid_origin>]/#<_y_step_size>]
#<_grid_x_0> = fix[#<_grid_x_w>]
#<_grid_y_0> = fix[#<_grid_y_w>]
#<_grid_x_1> = fup[#<_grid_x_w>]
#<_grid_y_1> = fup[#<_grid_y_w>]
#<_cell_x_w> = [#<_grid_x_w> - #<_grid_x_0>]
#<_cell_y_w> = [#<_grid_y_w> - #<_grid_y_0>]
(Bilinear interpolation equations from http://en.wikipedia.org/wiki/Bilinear_interpolation)
#<F00> = #[1000 + #<_grid_x_0> + #<_grid_y_0> * #<_x_grid_lines>]
#<F01> = #[1000 + #<_grid_x_0> + #<_grid_y_1> * #<_x_grid_lines>]
#<F10> = #[1000 + #<_grid_x_1> + #<_grid_y_0> * #<_x_grid_lines>]
#<F11> = #[1000 + #<_grid_x_1> + #<_grid_y_1> * #<_x_grid_lines>]
#<b1> = #<F00>
#<b2> = [#<F10> - #<F00>]
#<b3> = [#<F01> - #<F00>]
#<b4> = [#<F00> - #<F10> - #<F01> + #<F11>]
#<z_adj> = [#<b1> + #<b2>*#<_cell_x_w> + #<b3>*#<_cell_y_w> + #<b4>*#<_cell_x_w>*#<_cell_y_w>]
#<z_etch> = [#<_etch_depth> + #<z_adj>]
(ignore trivial z axis moves)
"""
intro.append(line)
line = "O202 if [abs[#<z_etch> - #<_last_z_etch> ] lt %.4f]" % (z_trivial)
intro.append(line)
line = """
#<z_etch> = #<_last_z_etch>
O202 else
#<_last_z_etch> = #<z_etch>
O202 endif
(now do the move)
G01 X#<_x_way> Y#<_y_way> Z[#<z_etch>] F[#<_etch_speed>]
(and then go to the next way point)
#<waypoint_number> = [#<waypoint_number> - 1]
O201 endwhile
O200 endsub
( Probe section )
( This section probes the grid and writes the probe results )
( sequentially to variables #1000, #1001, #1002 etc etc )
( such that the result at x,y on the grid is stored in )
( #[1000 + x + y*[x_grid_lines]] )
( You'll run out of memory if you probe more than 4,000 points! )
#<_grid_x> = 0
#<_grid_y> = 0
G00 Z#<_z_safety>
G00 X#<_x_grid_origin> Y#<_y_grid_origin>
O001 while [#<_grid_y> lt #<_y_grid_lines>]
G00 Y[#<_y_grid_origin> + #<_y_step_size> * #<_grid_y>]
O002 if [[#<_grid_y> / 2] - fix[#<_grid_y> / 2] eq 0]
#<_grid_x> = 0
O003 while [#<_grid_x> lt #<_x_grid_lines>]
O100 call (probe subroutine)
#<_grid_x> = [#<_grid_x> + 1]
O003 endwhile
O002 else
#<_grid_x> = #<_x_grid_lines>
O004 while [#<_grid_x> gt 0]
#<_grid_x> = [#<_grid_x> - 1]
O100 call (probe subroutine)
O004 endwhile
O002 endif
#<_grid_y> = [#<_grid_y> + 1]
O001 endwhile
"""
intro.append(line)
line = "G00 Z%.4f \n" % (z_probe_detach)
intro.append(line)
line = """
( Main ngc program section )
( Python has replaced all G01 etch moves from original file eg G01 Xaa Ybb Zcc Fdd )
( with an adjusted etch move in the format O200 sub [x_start] [y_start] [aa] [bb] )
( O200 is the etch subroutine )
(MSG, OK folks - power up the mill...)
M00
"""
intro.append(line)
# create and then save the output file
file_out = intro + file_out
f = open(file_out_name, 'w')
for line in file_out:
f.write(line)
f.close()
# now output the altered ngc file to the screen
for line in file_out:
print line,
else:
from Tkinter import *
def OK() : top.destroy()
top = Tk()
top.title(" Check file")
var = IntVar()
L1 = Label(top, text="")
L2 = Label(top, text="Sorry, no etch moves found in that file.", font = "12")
L3 = Label(top, text="")
L4 = Label(top, text="Check file name, units, etch move definition etc.", font = "12")
L5 = Label(top, text="")
L6 = Label(top, text="")
B1 = Button(top, text ="OK", command = OK, font = "18")
L1.pack()
L2.pack(anchor = W, padx=45)
L3.pack()
L4.pack(anchor = W, padx=45)
L5.pack()
B1.pack()
L6.pack()
top.mainloop()