- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

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

#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.

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

Автор: yarston

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/c-3/241081

Ссылки в тексте:

[1] из этой статьи: https://habrahabr.ru/post/252189/

[2] репозиторий: https://bitbucket.org/Yarston/simple3dhost

[3] Источник: https://habrahabr.ru/post/321314/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox