1. Introducción

La mayoría de las pantallas de LinuxCNC tienen la capacidad de pasar archivos cargados por un programa de filtro o usar el programa filtro para crear código G. Este filtro puede realizar cualquier tarea deseada: algo tan simple como asegurarse que el archivo termina con M2, o algo tan complicado como generar código G a partir de una imagen.

2. Configurar el INI para programas filtro

La sección [FILTER] del archivo INI controla cómo funcionan los filtros. Primero, para cada tipo de archivo, se escribe una línea PROGRAM_EXTENSION. Luego, se especifica el programa a ejecutar para cada tipo de archivo. Este programa recibe el nombre del archivo de entrada como su primer argumento, y debe escribir código rs274ngc en la salida estándar. Esta salida es lo que se mostrará en el área de texto, se previsualizará en el área de visualización y será ejecutado por LinuxCNC con Ejecutar. Las líneas siguientes agregan soporte para el convertidor image-to-gcode incluido en LinuxCNC:

[FILTER]
PROGRAM_EXTENSION = .png,.gif Imagen de profundidad en escala de grises
png = image-to-gcode
gif = image-to-gcode

También es posible especificar un intérprete:

PROGRAM_EXTENSION = .py Script Python
py = python

De esta manera, cualquier script de Python se puede abrir, y su salida es tratada como código G. Un ejemplo de tal script está disponible en nc_files/holecircle.py. Este script crea código G para perforar una serie de agujeros a lo largo de la circunferencia de un círculo.

Agujeros circulares
Figura 1. Agujeros circulares

Si un programa filtro envía líneas a stderr de la forma:

FILTER_PROGRESS=10

Establecerá la barra de progreso de la pantalla en el porcentaje dado (10 en este caso). Esta característica debería utilizarse en cualquier filtro que se ejecute durante mucho tiempo.

3. Crear programas filtro basados en Python

Aquí está un ejemplo muy básico de la mecánica de filtrado: Cuando se ejecuta mediante una pantalla de LinuxCNC que ofrezca filtrado por programa, producirá y escribirá una línea de código G cada 100ma de segundo en la salida estándar. También enviará un mensaje de progreso al flujo de error estándar de UNIX. Si hubiera un error publicará un mensaje de error y saldrá con un código de salida 1.

import time
import sys

for i in range(0,100):
    try:
        # simula tiempo de cálculo
        time.sleep(.1)

        # emite una línea código G
        print('G0 X1', file=sys.stdout)

        # actualiza el progreso
        print('FILTER_PROGRESS={}'.format(i), file=sys.stderr)
    except:
        # Esto provoca un mensaje de error
        print('Error; pero esto es solo una prueba', file=sys.stderr)
        raise SystemExit(1)

Aquí está un programa similar que en realidad puede filtrar. Coloca una ventana de diálogo PyQt5 con un botón de cancelación. Luego lee el programa línea por línea y las pasa a la salida estándar. Mientras transcurre, actualiza cualquier proceso escuchando la salida de error estándar.

#!/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("Prueba de filtrado con GUI")

        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("La ruta: '{}' no existe:".format(path), file=sys.stderr)
            raise SystemExit(1)

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

        # calcula el porcentaje del intervalo de actualización
        self.bump = 100/float(len(self.temp))

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

    def reject(self):
        # Esto da un mensaje de error
        print('Solicitaste cancelar antes de terminar.', file=sys.stderr)
        raise SystemExit(1)

    def process(self):
        try:
            # obtiene la siguiente línea de código
            codeLine = self.temp[self.line]

            # procesa la línea de algún modo

            # saca el código procesado
            print(codeLine, file=sys.stdout)
            self.line +=1

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

            # si acabó termina sin error/mensaje de error
            if self._percentDone >= 99:
                print('FILTER_PROGRESS=-1', file=sys.stderr)
                self.infile.close()
                raise SystemExit(0)

        except Exception as e:
            # Esto da un mensaje de error
            print(('Sucedió algo malo:',e), file=sys.stderr)
            # esto señaliza que el mensaje de error debería mostrarse
            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_() )