holecircle.py
1 import sys, os 2 BASE = os.environ['EMC2_HOME'] 3 sys.path.insert(0, os.path.join(BASE, "lib", "python")) 4 5 import math 6 7 def _(s): return s 8 9 def ui(): 10 import Tkinter 11 import pickle 12 import nf 13 import rs274.options 14 import os 15 16 app = Tkinter.Tk() 17 rs274.options.install(app) 18 app.tk.call("source", os.path.join(BASE, "share", "axis", "tcl", "combobox.tcl")) 19 20 app.wm_title(_("Circular Holes")) 21 app.wm_iconname(_("Circular Holes")) 22 23 prev = Tkinter.Canvas(app, width=200, height=200) 24 f = Tkinter.Frame(app) 25 b = Tkinter.Frame(app) 26 prev.grid(row=0, column=0, sticky="nw") 27 f.grid(row=0, column=1, sticky="nw") 28 b.grid(row=1, column=0, columnspan=2, sticky="ne") 29 30 validate_float = "expr {[regexp {^-?([0-9]+(\.[0-9]*)?|\.[0-9]+|)$} %P]}" 31 validate_int = "expr {[regexp {^-?([0-9]+|)$} %P]}" 32 validate_posfloat = "expr {[regexp {^?([0-9]+(\.[0-9]*)?|\.[0-9]+|)$} %P]}" 33 validate_posint = "expr {[regexp {^([0-9]+|)$} %P]}" 34 def posfloatentry(f, v): 35 var = Tkinter.DoubleVar(f) 36 var.set(v) 37 w = Tkinter.Entry(f, textvariable=var, validatecommand=validate_posfloat, validate="all", width=10) 38 return w, var 39 40 def floatentry(f, v): 41 var = Tkinter.DoubleVar(f) 42 var.set(v) 43 w = Tkinter.Entry(f, textvariable=var, validatecommand=validate_float, validate="all", width=10) 44 return w, var 45 46 def posintentry(f, v): 47 var = Tkinter.IntVar(f) 48 var.set(v) 49 w = Tkinter.Entry(f, textvariable=var, validatecommand=validate_posint, validate="all", width=10) 50 return w, var 51 52 53 def intentry(f, v): 54 var = Tkinter.IntVar(f) 55 var.set(v) 56 w = Tkinter.Entry(f, textvariable=var, validatecommand=validate_int, validate="all", width=10) 57 return w, var 58 59 def checkbutton(k, v): 60 var = Tkinter.BooleanVar(f) 61 var.set(v) 62 g = Tkinter.Frame(f) 63 w = Tkinter.Checkbutton(g, variable=var, text="Yes") 64 w.pack(side="left") 65 return g, var 66 67 def intscale(k, v, min=1, max = 100): 68 var = Tkinter.IntVar(f) 69 var.set(v) 70 g = Tkinter.Frame(f, borderwidth=0) 71 w = Tkinter.Scale(g, orient="h", variable=var, from_=min, to=max, showvalue=False) 72 l = Tkinter.Label(g, textvariable=var, width=3) 73 l.pack(side="left") 74 w.pack(side="left", fill="x", expand=1) 75 return g, var 76 77 def optionmenu(k, v, *options): 78 options = list(options) 79 def trace(*args): 80 try: 81 var.set(options.index(svar.get())) 82 except ValueError: 83 pass 84 85 try: 86 opt = options[v] 87 except (TypeError, IndexError): 88 v = 0 89 opt = options[0] 90 91 var = Tkinter.IntVar(f) 92 var.set(v) 93 svar = Tkinter.StringVar(f) 94 svar.set(options[v]) 95 svar.trace("w", trace) 96 wp = f._w.rstrip(".") + ".c" + svar._name 97 f.tk.call("combobox::combobox", wp, "-editable", 0, "-width", 98 max(len(opt) for opt in options)+3, "-textvariable", svar._name, 99 "-background", "white") 100 f.tk.call(wp, "list", "insert", "end", *options) 101 w = nf.makewidget(f, Tkinter.Widget, wp) 102 return w, var 103 104 rc = os.path.expanduser("~/.holecirclerc") 105 constructors = [ 106 ("units", lambda f, v: optionmenu(f, v, _("G20 (in)"), _("G21 (mm)"))), 107 ("cx", floatentry), 108 ("cy", floatentry), 109 ("th0", floatentry), 110 ("inc", floatentry), 111 ("rad", posfloatentry), 112 ("count", posintentry), 113 ("feedrate", posfloatentry), 114 ("depth", floatentry), 115 ("dwell", posfloatentry), 116 ("retract", floatentry), 117 ] 118 119 defaults = dict( 120 cx = 0, 121 cy = 0, 122 th0 = 0, 123 inc = 15, 124 count = 6, 125 feedrate = 8, 126 depth=-.1, 127 retract=.1, 128 units=0, 129 dwell=0, 130 rad=1 131 ) 132 133 texts = dict( 134 units=_("Units"), 135 rad=_("Radius"), 136 cx=_("Center X"), 137 cy=_("Center Y"), 138 th0=_("Start Angle"), 139 inc=_("Increment Angle"), 140 count=_("Hole Count"), 141 feedrate=_("Feed Rate"), 142 depth=_("Hole Depth"), 143 retract=_("Retract Height"), 144 dwell=("Dwell (0=no dwell)"), 145 ) 146 147 try: 148 defaults.update(pickle.load(open(rc, "rb"))) 149 except (IOError, pickle.PickleError): pass 150 151 vars = {} 152 widgets = {} 153 for j, (k, con) in enumerate(constructors): 154 v = defaults[k] 155 text = texts.get(k, k.replace("_", " ")) 156 lab = Tkinter.Label(f, text=text) 157 widgets[k], vars[k] = con(f, v) 158 lab.grid(row=j, column=0, sticky="w") 159 widgets[k].grid(row=j, column=1, sticky="ew") 160 161 def update_preview(*args): 162 prev.delete("all") 163 try: 164 count = vars['count'].get() 165 th0 = vars['th0'].get() 166 inc = vars['inc'].get() 167 except ValueError: return 168 for i in range(count): 169 th = (th0 + i * inc) * math.pi / 180 170 x = 100 + 75 * math.cos(th) 171 y = 100 - 75 * math.sin(th) 172 prev.create_oval((x-4,y-4,x+4,y+4), fill='black') 173 174 def update_ok(*args): 175 result = True 176 for i in vars.values(): 177 try: 178 i.get() 179 except ValueError: 180 result = False 181 break 182 if result: bb.configure(state="normal") 183 else: bb.configure(state="disabled") 184 # This line creates an error when you load holecircle twice 185 # from inside linuxcnc eg. gladevcp filechooser or AXIS GUI 186 #print >>sys.stderr, "update_ok", args 187 188 vars['count'].trace('w', update_preview) 189 vars['inc'].trace('w', update_preview) 190 vars['th0'].trace('w', update_preview) 191 192 for i in vars.values(): i.trace('w', update_ok) 193 194 update_preview() 195 196 status = Tkinter.IntVar() 197 bb = Tkinter.Button(b, text=_("OK"), command=lambda:status.set(1), width=8, default="active") 198 bb.pack(side="left", padx=4, pady=4) 199 bc = Tkinter.Button(b, text=_("Cancel"), command=lambda:status.set(-1), width=8, default="normal") 200 bc.pack(side="left", padx=4, pady=4) 201 202 app.bind("<Escape>", lambda evt: bc.invoke()) 203 app.bind("<Return>", lambda evt: bb.invoke()) 204 app.wm_protocol("WM_DELETE_WINDOW", lambda: bc.invoke()) 205 app.wm_resizable(0,0) 206 207 app.wait_visibility() 208 app.tk.call("after", "idle", ("after", "idle", "focus [tk_focusNext .]")) 209 #app.tk_focusNext().focus() 210 app.wait_variable(status) 211 212 if status.get() == -1: 213 raise SystemExit(1) 214 215 for k, v in vars.items(): 216 defaults[k] = v.get() 217 218 app.destroy() 219 220 pickle.dump(defaults, open(rc, "wb")) 221 222 return defaults 223 224 unitcodes = ['G20', 'G21'] 225 u = ui() 226 print unitcodes[u['units']] 227 print "F%.1f" % u['feedrate'] 228 229 count = u['count'] 230 th0 = u['th0'] 231 inc = u['inc'] 232 depth = u['depth'] 233 retract = u['retract'] 234 cx = u['cx'] 235 cy = u['cy'] 236 rad = u['rad'] 237 238 if u['dwell']: cycle = "G82 P% 8.4f" % u['dwell'] 239 else: cycle = "G81" 240 for i in range(count): 241 th = (th0 + i * inc) * math.pi / 180 242 x = cx + rad * math.cos(th) 243 y = cy + rad * math.sin(th) 244 print "%s X% 8.4f Y% 8.4f Z% 8.4f R% 8.4f" % (cycle, x, y, depth, retract) 245 print "M2"