📄 image-to-gcode.py
字号:
#!/usr/bin/python## image-to-gcode is free software; you can redistribute it and/or modify## it under the terms of the GNU General Public License as published by the## Free Software Foundation; either version 2 of the License, or (at your## option) any later version. image-to-gcode is distributed in the hope ## that it will be useful, but WITHOUT ANY WARRANTY; without even the implied## warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See## the GNU General Public License for more details. You should have## received a copy of the GNU General Public License along with image-to-gcode;## if not, write to the Free Software Foundation, Inc., 59 Temple Place,## Suite 330, Boston, MA 02111-1307 USA## ## image-to-gcode.py is Copyright (C) 2005 Chris Radek## chris@timeguy.com## image-to-gcode.py is Copyright (C) 2006 Jeff Epler## jepler@unpy.netimport sys, osBASE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))sys.path.insert(0, os.path.join(BASE, "lib", "python"))import gettext;gettext.install("axis", localedir=os.path.join(BASE, "share", "locale"), unicode=True)import Image, numarrayimport numarray.ieeespecial as ieeefrom rs274.author import Gcodeimport rs274.optionsfrom math import *import operatorepsilon = 1e-5def ball_tool(r,rad): s = -sqrt(rad**2-r**2) return sdef endmill(r,dia): return 0def vee_common(angle): slope = tan(angle * pi / 180) def f(r, dia): return r * slope return ftool_makers = [ ball_tool, endmill, vee_common(45), vee_common(60)]def make_tool_shape(f, wdia, resp): res = 1. / resp dia = int(wdia*res+.5) wrad = wdia/2. if dia < 2: dia = 2 n = numarray.array([[ieee.plus_inf] * dia] * dia, type="Float32") hdia = dia / 2. l = [] for x in range(dia): for y in range(dia): r = hypot(x-hdia, y-hdia) * resp if r < wrad: z = f(r, wrad) l.append(z) n[x,y] = z n = n - n.min() return ndef amax(seq): res = 0 for i in seq: if abs(i) > abs(res): res = i return resdef group_by_sign(seq, slop=sin(pi/18), key=lambda x:x): sign = None subseq = [] for i in seq: ki = key(i) if sign is None: subseq.append(i) if ki != 0: sign = ki / abs(ki) else: subseq.append(i) if sign * ki < -slop: sign = ki / abs(ki) yield subseq subseq = [i] if subseq: yield subseqclass Convert_Scan_Alternating: def __init__(self): self.st = 0 def __call__(self, primary, items): st = self.st = self.st + 1 if st % 2: items.reverse() if st == 1: yield True, items else: yield False, items def reset(self): self.st = 0class Convert_Scan_Increasing: def __call__(self, primary, items): yield True, items def reset(self): passclass Convert_Scan_Decreasing: def __call__(self, primary, items): items.reverse() yield True, items def reset(self): passclass Convert_Scan_Upmill: def __init__(self, slop = sin(pi / 18)): self.slop = slop def __call__(self, primary, items): for span in group_by_sign(items, self.slop, operator.itemgetter(2)): if amax([it[2] for it in span]) < 0: span.reverse() yield True, span def reset(self): passclass Convert_Scan_Downmill: def __init__(self, slop = sin(pi / 18)): self.slop = slop def __call__(self, primary, items): for span in group_by_sign(items, self.slop, operator.itemgetter(2)): if amax([it[2] for it in span]) > 0: span.reverse() yield True, span def reset(self): passclass Reduce_Scan_Lace: def __init__(self, converter, slope, keep): self.converter = converter self.slope = slope self.keep = keep def __call__(self, primary, items): slope = self.slope keep = self.keep if primary: idx = 3 test = operator.le else: idx = 2 test = operator.ge def bos(j): return j - j % keep def eos(j): if j % keep == 0: return j return j + keep - j%keep for i, (flag, span) in enumerate(self.converter(primary, items)): subspan = [] a = None for i, si in enumerate(span): ki = si[idx] if a is None: if test(abs(ki), slope): a = b = i else: if test(abs(ki), slope): b = i else: if i - b < keep: continue yield True, span[bos(a):eos(b+1)] a = None if a is not None: yield True, span[a:] def reset(self): self.primary.reset()unitcodes = ['G20', 'G21']convert_makers = [ Convert_Scan_Increasing, Convert_Scan_Decreasing, Convert_Scan_Alternating, Convert_Scan_Upmill, Convert_Scan_Downmill ]def progress(a, b): if os.environ.has_key("AXIS_PROGRESS_BAR"): print >>sys.stderr, "FILTER_PROGRESS=%d" % int(a*100./b+.5) sys.stderr.flush()class Converter: def __init__(self, image, units, tool_shape, pixelsize, pixelstep, safetyheight, \ tolerance, feed, convert_rows, convert_cols, cols_first_flag, entry_cut, spindle_speed, roughing_offset, roughing_delta, roughing_feed): self.image = image self.units = units self.tool = tool_shape self.pixelsize = pixelsize self.pixelstep = pixelstep self.safetyheight = safetyheight self.tolerance = tolerance self.base_feed = feed self.convert_rows = convert_rows self.convert_cols = convert_cols self.cols_first_flag = cols_first_flag self.entry_cut = entry_cut self.spindle_speed = spindle_speed self.roughing_offset = roughing_offset self.roughing_delta = roughing_delta self.roughing_feed = roughing_feed self.cache = {} w, h = self.w, self.h = image.shape ts = self.ts = tool_shape.shape[0] self.h1 = h - ts self.w1 = w - ts self.tool_shape = tool_shape * self.pixelsize * ts / 2; def one_pass(self): g = self.g g.set_feed(self.feed) if self.convert_cols and self.cols_first_flag: self.g.set_plane(19) self.mill_cols(self.convert_cols, True) if self.convert_rows: g.safety() if self.convert_rows: self.g.set_plane(18) self.mill_rows(self.convert_rows, not self.cols_first_flag) if self.convert_cols and not self.cols_first_flag: self.g.set_plane(19) if self.convert_rows: g.safety() self.mill_cols(self.convert_cols, not self.convert_rows) if self.convert_cols: self.convert_cols.reset() if self.convert_rows: self.convert_rows.reset() g.safety() def convert(self): self.g = g = Gcode(safetyheight=self.safetyheight, tolerance=self.tolerance, spindle_speed=self.spindle_speed, units=self.units) g.begin() g.continuous(self.tolerance) g.safety() if self.roughing_delta and self.roughing_offset: base_image = self.image rough = make_tool_shape(ball_tool, 2*self.roughing_offset, self.pixelsize) w, h = base_image.shape tw, th = rough.shape w1 = w + tw h1 = h + th nim1 = numarray.zeros((w1, h1), 'Float32') + base_image.min() nim1[tw/2:tw/2+w, th/2:th/2+h] = base_image self.image = numarray.zeros((w,h), type="Float32") for j in range(0, w): progress(j,w) for i in range(0, h): self.image[j,i] = (nim1[j:j+tw,i:i+th] - rough).max() self.feed = self.roughing_feed r = -self.roughing_delta m = self.image.min() self.ro = self.roughing_offset while r > m: self.rd = r self.one_pass() r = r - self.roughing_delta if r < m + epsilon: self.rd = m self.one_pass() self.image = base_image self.cache.clear() self.feed = self.base_feed self.ro = 0 self.rd = self.image.min() self.one_pass() g.end() def get_z(self, x, y): try: return min(0, max(self.rd, self.cache[x,y]) + self.ro) except KeyError: m1 = self.image[y:y+self.ts, x:x+self.ts] self.cache[x,y] = d = (m1 - self.tool).max() return min(0, max(self.rd, d) + self.ro) def get_dz_dy(self, x, y): y1 = max(0, y-1) y2 = min(self.image.shape[0]-1, y+1) dy = self.pixelsize * (y2-y1) return (self.get_z(x, y2) - self.get_z(x, y1)) / dy def get_dz_dx(self, x, y): x1 = max(0, x-1) x2 = min(self.image.shape[1]-1, x+1) dx = self.pixelsize * (x2-x1) return (self.get_z(x2, y) - self.get_z(x1, y)) / dx def mill_rows(self, convert_scan, primary): w1 = self.w1; h1 = self.h1; pixelsize = self.pixelsize; pixelstep = self.pixelstep jrange = range(0, w1, pixelstep) if w1-1 not in jrange: jrange.append(w1-1) irange = range(h1) for j in jrange: progress(jrange.index(j), len(jrange)) y = (w1-j) * pixelsize scan = [] for i in irange: x = i * pixelsize milldata = (i, (x, y, self.get_z(i, j)), self.get_dz_dx(i, j), self.get_dz_dy(i, j)) scan.append(milldata) for flag, points in convert_scan(primary, scan): if flag: self.entry_cut(self, points[0][0], j, points) for p in points: self.g.cut(*p[1]) self.g.flush() def mill_cols(self, convert_scan, primary): w1 = self.w1; h1 = self.h1; pixelsize = self.pixelsize; pixelstep = self.pixelstep jrange = range(0, h1, pixelstep) irange = range(w1) if h1-1 not in jrange: jrange.append(h1-1) jrange.reverse() for j in jrange: progress(jrange.index(j), len(jrange)) x = j * pixelsize scan = [] for i in irange: y = (w1-i) * pixelsize milldata = (i, (x, y, self.get_z(j, i)), self.get_dz_dy(j, i), self.get_dz_dx(j, i)) scan.append(milldata) for flag, points in convert_scan(primary, scan): if flag: self.entry_cut(self, j, points[0][0], points) for p in points: self.g.cut(*p[1]) self.g.flush()def convert(*args, **kw): return Converter(*args, **kw).convert()class SimpleEntryCut: def __init__(self, feed): self.feed = feed def __call__(self, conv, i0, j0, points): p = points[0][1] if self.feed: conv.g.set_feed(self.feed) conv.g.safety() conv.g.rapid(p[0], p[1]) if self.feed: conv.g.set_feed(conv.feed)def circ(r,b): """\Calculate the portion of the arc to do so that none is above thesafety height (that's just silly)""" z = r**2 - (r-b)**2 if z < 0: z = 0 return z**.5class ArcEntryCut: def __init__(self, feed, max_radius): self.feed = feed self.max_radius = max_radius def __call__(self, conv, i0, j0, points): if len(points) < 2: p = points[0][1] if self.feed: conv.g.set_feed(self.feed) conv.g.safety() conv.g.rapid(p[0], p[1]) if self.feed: conv.g.set_feed(conv.feed) return p1 = points[0][1] p2 = points[1][1] z0 = p1[2] lim = int(ceil(self.max_radius / conv.pixelsize)) r = range(1, lim) if self.feed: conv.g.set_feed(self.feed)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -