Хотя данная статья и является своеобразным ответом на 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", "G90\nG1 X%.1f F3000\n",
    "Y move", "G90\nG1 Y%.1f F3000\n",
    "Z move", "G90\nG1 Z%.1f F3000\n",
    "Extrude", "G90\nG1 E%.1f F3000\n",
    "End", "M303 E0 S%.1f\n",
    "Bed", "M303 E1 S%.1f\n",
    "X home", "G28 X0\n",
    "Y home", "G28 Y0\n",
    "Z home", "G28 Z0\n",
    "Beep", "M300 S3000 P100\n",
    "End Off", "M303 E0 S0\n",
    "Bed Off", "M303 E1 S0\n"
};
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.

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

Комментарии (4)


  1. saluev
    07.02.2017 12:11
    +7

    Подсказка: можно склеить вообще все синтаксические конструкции (кроме #include) в одну строку и назвать статью «Хост-контроллер 3D-принтера в 1 строку на С++».


    1. DeadKnight
      07.02.2017 12:52
      +3

      Вот мне тоже интересно, неужели автору так важно писать такие конструкции в одну строку, вместо того, чтобы написать читаемый отформатированный код?

      Может у него компилятор какой-то особенный, который отформатированный код не понимает?


      1. withkittens
        07.02.2017 13:36
        +2

        Если код отформатировать, то и статьи-то не получится.


  1. Satus
    08.02.2017 03:50
    +2

    Мне кажется, статью можно было назвать «Хост контроллер 3D принтера в 50 строк на С++» и всё равно звучало бы впечатляюще. Зато код хотя бы можно было бы прочитать.