1. Introduction
Most of LinuxCNC’s screens have the ability to send loaded files through a filter program or use the filter program to make G-code. Such a filter can do any desired task: Something as simple as making sure the file ends with M2, or something as complicated as generating G-code from an image.
2. Setting up the INI for Program Filters
The [FILTER] section of the INI file controls how filters work. First, for each type of file, write a PROGRAM_EXTENSION line. Then, specify the program to execute for each type of file. This program is given the name of the input file as its first argument, and must write rs274ngc code to standard output. This output is what will be displayed in the text area, previewed in the display area, and executed by LinuxCNC when Run. The following lines add support for the image-to-gcode converter included with LinuxCNC:
[FILTER] PROGRAM_EXTENSION = .png,.gif Greyscale Depth Image png = image-to-gcode gif = image-to-gcode
It is also possible to specify an interpreter:
PROGRAM_EXTENSION = .py Python Script py = python
In this way, any Python script can be opened, and its output is treated as G-code. One such example script is available at nc_files/holecircle.py. This script creates G-code for drilling a series of holes along the circumference of a circle.
If the filter program sends lines to stderr of the form:
FILTER_PROGRESS=10
It will set the screens progress bar to the given (10 in this case) percentage. This feature should be used by any filter that runs for a long time.
3. Making Python Based Filter Programs
Here is a very basic example of the filtering mechanics: When run through a Linucnc screen that offers program filtering, it will produce and write a line of G-code every 100th of a second to standard output. It also sends a progress message out to the Unix standard error stream. If there was an error it would post an error message and exit with an exitcode of 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)
Here is a similar program but it actually could filter. It puts up a PyQt5 dialog with a cancel button. Then it reads the program line by line and passes it to standard output. As it goes along, it updates any process listening to standard error output.
#!/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_() )