Хост контроллер 3D принтера в 16 строк на С++

в 8:50, , рубрики: 3D-печать, c++, fltk, ненормальное программирование, метки:

Хотя данная статья и является своеобразным ответом на 30-строчники на JS, поводом к её появлению послужила вполне практическая проблема.

На днях, когда понадобилось распечатать детальку, вдруг обнаружилось, что привычный Repetier Host просто не стартует, заявляя о несовместимости с версией Mono (4.26), когда ему нужна > 4.0. Вот такая вот «кроссплатформенность».

После запуска прилагаемого конфигурационного скрипта долго что-то качалось и устанавливалось, но ничего так и не заработало. Выяснять кто виноват и что делать желания не было, поэтому перешёл к следующему претенденту на рабочий инструмент — Cura. Попробовал — работает, но вручную печатающую головку там не подвигать, погуглил более новую версию Cura — по отзывам, оттуда убрали RepRap принтеры, даже если их можно вручную как-то вернуть, довольно некрасиво для компании, заявляющей, что делает опенсорс.

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

#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>

#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Counter.H>
#include <FL/Fl_Widget.H>
#include <FL/Fl.H>

const char* data[] = {
    "X move", "G90nG1 X%.1f F3000n",
    "Y move", "G90nG1 Y%.1f F3000n",
    "Z move", "G90nG1 Z%.1f F3000n",
    "Extrude", "G90nG1 E%.1f F3000n",
    "End", "M303 E0 S%.1fn",
    "Bed", "M303 E1 S%.1fn",
    "X home", "G28 X0n",
    "Y home", "G28 Y0n",
    "Z home", "G28 Z0n",
    "Beep", "M300 S3000 P100n",
    "End Off", "M303 E0 S0n",
    "Bed Off", "M303 E1 S0n"
};
Fl_Counter* counters[6];
int buff[10];
int main() {
    Fl_Window window(310, 310, "Host control");
    window.begin();
    for (int i = 0; i < 12; i++) {
        Fl_Widget* widget = i < 6 ? (Fl_Widget*) (counters[i] = new Fl_Counter(10, 10 + i * 50, 200, 30, data[i * 2])) : (Fl_Widget*) new Fl_Button(220, 10 + (i - 6) * 50, 80, 30, data[i * 2]);
        if (i < 6) ((Fl_Counter*) widget)->step(1, 10);
        widget->user_data((int*) i);
        widget->callback([](Fl_Widget* w, void* inner) {if (write(buff[9] == 0 ? (buff[9] = open("/dev/ttyUSB0", O_RDWR | O_NONBLOCK | O_NDELAY)) : buff[9], (char*) buff, sprintf((char*) buff, data[1 + 2 * reinterpret_cast<uintptr_t> (inner)], ((Fl_Counter*) w)->value())) > 0 && reinterpret_cast<uintptr_t> (inner) < 9 && reinterpret_cast<uintptr_t> (inner) >= 6) counters[reinterpret_cast<uintptr_t> (inner) - 6]->value(0);});
    }
    window.end();
    window.show();
    return Fl::run();
}

data[] разбито исключительно для удобочитаемости, я считаю её за одну строку. Остальные строки уже не все хорошо выглядят, зато их количество после нескольких хаков стало красивым круглым числом.

Как видно, строки data[] разбиты на пары имя-gcode, что позволяет отрисовать довольно много виджетов (кнопки и счётчики) по этим данным в одном цикле, перебирающем эти строки. И кнопкам, и счётчикам передаётся слушатель событий в виде лямбда-выражения, но, к сожалению, FLTK не понимает настоящие лямбды, поэтому захват переменных сделать не получится, однако в виджет можно передать указатель, который потом передаётся в каллбэк, что здесь и было использовано. Номер виджета приводится к указателю, потом внутри лямбды указатель приводится обратно к целому числу, и, таким образом, лямбда может узнать номер виджета, из которого была вызвана.

Дальше по этому номеру берётся заготовка gcode из data[], подставляется число из аргумента лямбды, если она была вызвана из счётчика, и затем код пишется в последовательный порт. По-хорошему, надо было бы указать скорость передачи данных, но так совпало, что принтер хотя и понимает только 115200, завёлся, поэтому так и оставил. Ещё один хак — в качестве буфера для sprintf() был взят int[], что позволило последний элемент массива использовать для хранения файлового дескриптора open(), что вместе с «ленивым» открытием сэкономило пару строк.

В итоге, на 16 строк кода пришлось 12 виджетов, почти по одной строке на виджет, интересный результат. Скомпилировать можно командой g++ main.cpp -lfltk -o 3dhost, в системе должен стоять пакет fltk-devel.

Ссылка на репозиторий; не уверен, что буду там ещё что-то допиливать до полноценного приложения, но чем чёрт не шутит. Кстати, таким же образом можно управлять почти любым другим устройством по последовательному порту, лишь заменив коды и названия кнопок.

Автор: yarston

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js