Хочу выразить благодарность за книгу
"Язык программирования Python" авторов "Г. Россум, Ф.Л.Дж. Дрейк, Д.С. Откидач"
А еще вот это видео мне очень помогло:
https://www.youtube.com/watch?v=bZZTeKPRSLQ
Итак, основаная функция это понятное дело -
содать текст g-кода, который выводит в поток вывода(как результат функции). Т.е. при запуске скрипта из терминала - выводит текст g-кода в строку терминала.
Сам текст g-кода создает специальный класс с названием как ни странно
Gcode. Этот класс подключается через библиотеку rs274.
Вот так:
Код: Выделить всё
from rs274.author import Gcode
import rs274.options
Как я понял - библиотека скомпилированная, поэтому просто прочесть код - не получится, но на просторах и-нета я нашел текст. Между прочим написали эту библиотеку те же Chris Radek и Jeff Epler, кто написал image-to-gcode.
Итак текст библиотеки:
- gcodesaver.py.zip
- библиотека создания g-кода по массиву точек в формате (X,Y,Z)
- (2.76 КБ) 625 скачиваний
Значения полей формы сохраняются в файл
"~/.image2gcoderc" чтобы в следующий раз не выставлять снова тоже самое.
Поля формы задаются в функции
optionmenu массивы
constructors,
defaults и
texts
Код: Выделить всё
def optionmenu(*options): return lambda f, v: _optionmenu(f, v, *options)
rc = os.path.expanduser("~/.image2gcoderc")
constructors = [
("units", optionmenu(_("G20 (in)"), _("G21 (mm)"))),
("invert", checkbutton),
("normalize", checkbutton),
("expand", optionmenu(_("None"), _("White"), _("Black"))),
("tolerance", floatentry),
("pixel_size", floatentry),
("feed_rate", floatentry),
("plunge_feed_rate", floatentry),
("spindle_speed", floatentry),
("pattern", optionmenu(_("Rows"), _("Columns"), _("Rows then Columns"), _("Columns then Rows"))),
("converter", optionmenu(_("Positive"), _("Negative"), _("Alternating"), _("Up Milling"), _("Down Milling"))),
("depth", floatentry),
("pixelstep", intscale),
("tool_diameter", floatentry),
("safety_height", floatentry),
("tool_type", optionmenu(_("Ball End"), _("Flat End"), _("30 Degree"), _("45 Degree"), _("60 Degree"))),
("bounded", optionmenu(_("None"), _("Secondary"), _("Full"))),
("contact_angle", floatentry),
("roughing_offset", floatentry),
("roughing_depth", floatentry),
]
defaults = dict(
invert = False,
normalize = False,
expand = 0,
pixel_size = .006,
depth = 0.25,
pixelstep = 8,
tool_diameter = 1/16.,
safety_height = .012,
tool_type = 0,
tolerance = .001,
feed_rate = 12,
plunge_feed_rate = 12,
units = 0,
pattern = 0,
converter = 0,
bounded = 0,
contact_angle = 45,
spindle_speed = 1000,
roughing_offset = .1,
roughing_depth = .25,
)
texts = dict(
invert=_("Invert Image"),
normalize=_("Normalize Image"),
expand=_("Extend Image Border"),
pixel_size=_("Pixel Size (Units)"),
depth=_("Depth (units)"),
tolerance=_("Tolerance (units)"),
pixelstep=_("Stepover (pixels)"),
tool_diameter=_("Tool Diameter (units)"),
tool_type=_("Tool Type"),
feed_rate=_("Feed Rate (units per minute)"),
plunge_feed_rate=_("Plunge Feed Rate (units per minute)"),
units=_("Units"),
safety_height=_("Safety Height (units)"),
pattern=_("Scan Pattern"),
converter=_("Scan Direction"),
bounded=_("Lace Bounding"),
contact_angle=_("Contact Angle (degrees)"),
spindle_speed=_("Spindle Speed (RPM)"),
roughing_offset=_("Roughing offset (units, 0=no roughing)"),
roughing_depth=_("Roughing depth per pass (units)"),
)
...
Последовательность вызовов функций:
convert - one_pass - [mill_cols/mill_rows] - g.flush() - g.douglas()
основная функция создающая g-кода называется
convert
в ней сразу же инициализируется класс
Gcode базовыми параметрами типа "безопастная высота", "скорость шпинделя",...
Код: Выделить всё
self.g = g = Gcode(safetyheight=self.safetyheight,
tolerance=self.tolerance,
spindle_speed=self.spindle_speed,
units=self.units)
далее начинается
цикл по ПРЕДВАРИТЕЛЬНЫМ черновым проходам:
Код: Выделить всё
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
[color=#FF0000]self.one_pass()[/color]
r = r - self.roughing_delta
if r < m + epsilon:
self.rd = m
[color=#FF0000]self.one_pass()[/color]
self.image = base_image
self.cache.clear()
после черновых проходов создается основной проход:
Код: Выделить всё
self.feed = self.base_feed
self.ro = 0
self.rd = self.image.min()
[color=#FF0000]self.one_pass()[/color]
код создается через вызовы функции
one_pass. В зависимости от разных параметров вызываются или mill_cols или mill_rows...
Код: Выделить всё
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.[color=#FF0000]mill_cols[/color](self.convert_cols, True)
if self.convert_rows: g.safety()
if self.convert_rows:
self.g.set_plane(18)
self.[color=#FF0000]mill_rows[/color](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.[color=#FF0000]mill_cols[/color](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()
в эту(one_pass) функцию передаются настройки через класс
self, которые предварительно устанавливаются в convert-е
в
one_pass в свою очередь вызывает функции
mill_cols или
mill_rows в зависимости от настроек...
Код: Выделить всё
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))
#--------------------------------------
#Здесь я и определяю с какого и по какой пиксель - начинается и заканчивается рельеф, а что считать фоном
row_y_first = 0;
if self.Porog_depth == 0:
row_y_last = len(irange);
else:
row_y_last = 0;
if self.invert:
maxfon = self.Porog_depth*(-1)
else:
maxfon = (self.image.min()+self.Porog_depth);
for i in irange:
#**************************************************************************************************
#код определяющий высоту я взял из функции get_z() и переделал
#т.к. get_z() определяет высоту с учтом чернового прохода, а нужно брать оригинал...
try:
hhh1 = min(0, self.cache[i,j])
except KeyError:
m1 = self.image[j:j+self.ts,i:i+self.ts]
self.cache[i,j] = d = (m1 - self.tool).max()
hhh1 = min(0,d)
#**************************************************************************************************
if self.invert:
if hhh1 <= maxfon: row_y_last = i
else:
if row_y_last == 0: row_y_first = i
else:
if hhh1 >= maxfon: row_y_last = i
else:
if row_y_last == 0: row_y_first = i
if row_y_first > 0: row_y_first = row_y_first -1
if row_y_last < len(irange): row_y_last = row_y_last +1
#--------------------------------------
y = (w1-j) * pixelsize
scan = []
for i in irange:
if i >= row_y_first and i<= row_y_last:
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)
if len(scan) != 0:
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))
#--------------------------------------
#Здесь я и определяю с какого и по какой пиксель - начинается и заканчивается рельеф, а что считать фоном
row_y_first = 0;
if self.Porog_depth == 0:
row_y_last = len(irange);
else:
row_y_last = 0;
if self.invert:
maxfon = self.Porog_depth*(-1)
else:
maxfon = (self.image.min()+self.Porog_depth);
for i in irange:
#**************************************************************************************************
#код определяющий высоту я взял из функции get_z() и переделал
#т.к. get_z() определяет высоту с учтом чернового прохода, а нужно брать оригинал...
try:
hhh1 = min(0, self.cache[j,i])
except KeyError:
m1 = self.image[i:i+self.ts,j:j+self.ts]
self.cache[j,i] = d = (m1 - self.tool).max()
hhh1 = min(0,d)
#**************************************************************************************************
if self.invert:
if hhh1 <= maxfon: row_y_last = i
else:
if row_y_last == 0: row_y_first = i
else:
if hhh1 >= maxfon: row_y_last = i
else:
if row_y_last == 0: row_y_first = i
if row_y_first > 0: row_y_first = row_y_first -1
if row_y_last < len(irange): row_y_last = row_y_last +1
#--------------------------------------
x = j * pixelsize
scan = []
for i in irange:
if i >= row_y_first and i<= row_y_last:
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)
if len(scan) != 0:
for flag, points in м(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()
Функция entry_cut - создает код врезания в заготовку (такая вертикальная палочка в верхнем правом углу).
И самое главное:
self.g.flush() - создает текст g-кода по точкам предварительно заполненного массива self.g.cuts. Предварительно массив g.cuts заполняется значениями через функцию self.g.cut(*p[1])
Код: Выделить всё
def cut(self, x=None, y=None, z=None):
if self.cuts:
lastx, lasty, lastz = self.cuts[-1]
else:
lastx, lasty, lastz = self.lastx, self.lasty, self.lastz
if x is None: x = lastx
if y == None: y = lasty
if z == None: z = lastz
self.cuts.append([x,y,z])
def flush(self):
if not self.cuts: return
for x, y, z in douglas(self.cuts, self.tolerance):
self.move_common(x, y, z, gcode="G1")
self.cuts = []