Хочу выразить благодарность за книгу 
"Язык программирования 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 КБ) 654 скачивания
 
		
		
			 
Значения полей формы сохраняются в файл 
"~/.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 = []