1. Введение

Большинство экранов LinuxCNC имеют возможность отправлять загруженные файлы через filter program или использовать программу-фильтр для создания G-кода. Такой фильтр может выполнять любую желаемую задачу: что-то простое, например проверка того, что файл заканчивается на «M2», или что-то более сложное, например создание G-кода из изображения.

2. Настройка INI для программных фильтров

Раздел [FILTER] INI-файла управляет работой фильтров. Сначала для каждого типа файла напишите строку PROGRAM_EXTENSION. Затем укажите программу, которая будет выполняться для каждого типа файла. Этой программе присваивается имя входного файла в качестве первого аргумента, и она должна записать код rs274ngc в стандартный вывод. Этот вывод будет отображаться в текстовой области, просматриваться в области отображения и выполняться LinuxCNC при нажатии кнопки Run. Следующие строки добавляют поддержку конвертера image-to-gcode, включенного в LinuxCNC:

[FILTER]
PROGRAM_EXTENSION = .png,.gif Greyscale Depth Image
png = image-to-gcode
gif = image-to-gcode

Также возможно указать интерпретатор:

PROGRAM_EXTENSION = .py Python Script
py = python

Таким образом, любой скрипт Python может быть открыт, а его выходные данные обрабатываются как G-код. Один из таких примеров скрипта доступен по адресу nc_files/holecircle.py. Этот скрипт создает G-код для сверления серии отверстий по окружности.

Отверстия по кругу
Figure 1. Отверстия по кругу

Если программа фильтра отправляет в stderr строки вида:

FILTER_PROGRESS=10

Он установит индикатор выполнения экрана на заданный (в данном случае 10) процент. Эту функцию следует использовать любому фильтру, работающему в течение длительного времени.

3. Создание программ фильтров на основе Python

Вот очень простой пример механики фильтрации: при запуске экрана LinuxCNC, предлагающего программную фильтрацию, он будет генерировать и записывать строку G-кода каждую сотую долю секунды в стандартный вывод. Он также отправляет сообщение о ходе выполнения в стандартный поток ошибок Unix. Если произошла ошибка, он выведет сообщение об ошибке и завершит работу с кодом выхода 1.

import time
import sys

for i in range(0,100):
    try:
        # simulate calculation time
        time.sleep(.1)

        # output a line of G-code
        print('G0 X1', file=sys.stdout)

        # update progress
        print('FILTER_PROGRESS={}'.format(i), file=sys.stderr)
    except:
        # This causes an error message
        print('Error; But this was only a test', file=sys.stderr)
        raise SystemExit(1)

Вот похожая программа, но она действительно умеет фильтровать. Она открывает диалоговое окно PyQt5 с кнопкой отмены. Затем она считывает программу построчно и передает ее на стандартный вывод. По ходу дела она обновляет любой процесс, прослушивающий стандартный вывод ошибок.

#!/usr/bin/env python3

import sys
import os
import time

from PyQt5.QtWidgets import (QApplication, QDialog, QDialogButtonBox,
                            QVBoxLayout,QDialogButtonBox)
from PyQt5.QtCore import QTimer, Qt

class CustomDialog(QDialog):

    def __init__(self, path):
        super(CustomDialog, self).__init__(None)
        self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
        self.setWindowTitle("Filter-with-GUI Test")

        QBtn = QDialogButtonBox.Cancel

        self.buttonBox = QDialogButtonBox(QBtn)
        self.buttonBox.rejected.connect(self.reject)

        self.layout = QVBoxLayout()
        self.layout.addWidget(self.buttonBox)
        self.setLayout(self.layout)

        self.line = 0
        self._percentDone = 0

        if not os.path.exists(path):
            print("Path: '{}' doesn't exist:".format(path), file=sys.stderr)
            raise SystemExit(1)

        self.infile = open(path, "r")
        self.temp = self.infile.readlines()

        # calculate percent update interval
        self.bump = 100/float(len(self.temp))

        self._timer = QTimer()
        self._timer.timeout.connect(self.process)
        self._timer.start(100)

    def reject(self):
        # This provides an error message
        print('You asked to cancel before finished.', file=sys.stderr)
        raise SystemExit(1)

    def process(self):
        try:
            # get next line of code
            codeLine = self.temp[self.line]

            # process the line somehow

            # push out processed code
            print(codeLine, file=sys.stdout)
            self.line +=1

            # update progress
            self._percentDone += self.bump
            print('FILTER_PROGRESS={}'.format(int(self._percentDone)), file=sys.stderr)

            # if done end with no error/error message
            if self._percentDone >= 99:
                print('FILTER_PROGRESS=-1', file=sys.stderr)
                self.infile.close()
                raise SystemExit(0)

        except Exception as e:
            # This provides an error message
            print(('Something bad happened:',e), file=sys.stderr)
            # this signals the error message should be shown
            raise SystemExit(1)

if __name__ == "__main__":
    if (len(sys.argv)>1):
        path = sys.argv[1]
    else:
        path = None
    app = QApplication(sys.argv)
    w = CustomDialog(path=path)
    w.show()
    sys.exit( app.exec_() )