Привет, Хабр! Меня зовут Велеско Сергей, я Android разработчик в настоящее время и инженер-конструктор печатных плат в прошлой жизни. В этой статье я расскажу, как мне удалось применить знания, полученные в прошлой профессии, и написать простое Android приложение для просмотра Gerber-файлов.

Как будет идти повествование

Расскажу немного предыстории, затем пройдусь по требованиям и расскажу, почему выбрал те или иные технологии. Далее опишу сам процесс и в конце подведу итоги. Ссылку на репозиторий оставлю в конце статьи.

Немного предыстории

Идея написать просмотрщик/конвертер возникала у меня еще в 2018 году, когда на прошлой работе на шестерых конструкторов была единственная лицензия на софтину для чтения герберов с очень неудобным экспортом в растровое изображение. Экспорт фотошаблонов в растр был необходим для  документации и требовал неприличное количество ручных операций и времени. С этим нужно было что-то делать. И нужно было делать что-то с выгоранием от основной работы.

В общем, я взялся писать десктопный конвертер gerber -> bmp/png на Qt, который бы быстро (в многопоточном режиме) и за один раз конвертил все слои платы в изображения, обрезанные по размеру платы, и имеющие осмысленные названия файлов. Пришлось вспоминать С++ практически с нуля (за 6 лет после универа без практики забылись даже те примитивные навыки программирования, которые были), сидя вечерами с учебником и выполняя упражнения. Потом знакомство с Qt и изучение спецификации Gerber. За пол года приложение было написано, и успешно использовалось по назначению.

Мне так зашел процесс, что я стал всерьез интересоваться разработкой, и где-то через год Qt привел меня в андроид. Да так удачно привёл, что я сменил профессию конструктора на андроид-разработчика. На момент публикации чуть больше полугода профессионально занимаюсь нативной разработкой под андроид на kotlin. Тут еще на одну статью тянет, но вернемся к главной теме.

Зачем gerber viewer под андроид?

Во-первых решил отдать дань уважения печатным платам, так сказать, попрощаться достойно со всем, что связано с проектированием электроники. Во-вторых «обкатать» некоторые актуальные технологии в андроиде. В третьих — в маркете я нашел всего два аналогичных приложения, одно из которых платное, второе - с рекламой. Посчитал это возмутительным. Ближе к делу…

Требования

Как должно выглядеть приложение? Максимально просто. Два экрана. Первый — со списком открытых файлов, второй - с изображением содержимого открытых файлов.

Что должно делать приложение?

  1. Открывать гербер файлы

  2. Отображать список открытых файлов

  3. Каждый элемент списка должен содержать имя файла, переключатель видимости и цвет слоя на экране с изображением.

  4. Элемент должен удаляться из списка по свайпу.

  5. Парсинг и вообще вся обработка должна производиться в отдельном потоке.

  6. Изображение должно иметь зум, панораму и управление ими с  помощью привычных жестов.

Архитектура проекта

MVVM, потому что 1) подходит под задачу 2) имеет поддержку от гугл в виде architecture components. Также я решил делать проект многомодульным, т.к. сразу можно было выделить относительно независимые части приложения, выполняющие свою функцию. + в планах было выделить в отдельную kotlin библиотеку парсер. Ниже приведена примерная схема приложения с модулями и зависимостями.

Кратко по каждому модулю:

app — главный модуль приложения с экранами, вью моделями и репозиторием.

File Reader — модуль, который читает файлы и загружает их в оперативную память. На выходе список строк.

Syntax Parser — парсит строки из гербера. На выходе список команд.

Graphics Processor — обрабатывает команды. На выходе список графических объектов.

Logger — служебный модуль - обертка над timber.

Многопоточность

Логично было бы выполнять парсинг файлов и генерацию графических объектов в фоне, начиная сразу после добавления файла в список, показывая какой-нибудь лоадинг. Сначала я посматривал в сторону RxJava, которая предлагала относительно удобный способ работы с многопоточностью и плюшки в виде производительности при использовании rx источников/подписчиков вместо больших коллекций с командами гербера и графическими объектами. Но учитывая, что Rx теряет популярность, поднадоела за время учебы и используется на работе, было решено использовать корутины, с которыми у меня до этого не было опыта. 

Ui

Долго не сомневался при выборе, Single Activity (звучит громко, учитывая всего 2 экрана в приложении), экраны на Fragment. Compose показался слишком экспериментальным, отпугнул потенциально большими ресурсами на его параллельное освоение.

DI

Koin. Потому что простой и было интересно попробовать что-то кроме dagger. На работе пользуемся Hilt, но даже он показался чересчур сложным для такого простого проекта.

Рисование

View и Canvas. Смотрел в сторону SurfaceView но после непродолжительных экспериментов решил, что и производительности обычной View должно хватить. Естественно с учетом того, что на канве должны отрисовываться полностью готовые объекты, чтобы ничего не создавалось в методе onDraw. 

Сборка

В качестве эксперимента перевел все билд скрипты на Kotlin. Для удобства управления версиями запилил builsSrc.

Тестирование

JUnit для тестов. По плану должно быть много юнит-тестов.

Реализация

Модуль File Reader. Самый простой платформенно зависимый модуль. Все, что он делает — читает файл в список строк.

Модуль Syntax Parser. Gerber-файл представляет собой текстовый файл, описывающий поток gerber команд. Более подробно про Gerber вот тут. Как правило одна команда занимает одну строку. Поэтому было решено читать файл в список строк, и дальше работать с ним, используя регулярные выражения для парсинга конкретных команд. Учитывая относительно большое количество команд, хотелось сделать их последующую обработку в потоке удобной, т.е. без огромного условного оператора, чтобы команды сами могли позаботиться о своей обработке — тут ничего нового. Я завел вот такой интерфейс для команд:

interface GerberCommand {
   val lineNumber: Int
   fun perform(processor: CommandProcessor)
}

Каждая команда реализует этот интерфейс, предоставляя номер строки в файле (необходимо для информативных сообщений о синтаксических ошибках) и реализацию единственного метода perform, в котором команда сообщает некоему CommandProcessor, что она делает.

Использование абстракций позволило выполнить модуль простой kotlin-библиотекой, без зависимостей на платформу. (спасибо дяде Бобу)

В целом работа над парсером была не сильно сложной, но довольно рутинной. И таки осложнялась тотальным несоблюдением спецификации Gerber разработчиками САПР'ов (не буду показывать пальцем). Самой интересным этапом в разработке парсера оказалась реализация парсинга макро шаблонов — таких параметризованных моделей, с определениями кастомных графических примитивов и переменными с выражениями внутри. Вот пример описания макро шаблона:

%AMVB_RCRECTANGLE*
$3=$3X2*
21,1,$1-$3,$2,0,0,0*
21,1,$1,$2-$3,0,0,0*
$1=$1/2*
$2=$2/2*
$3=$3/2*
$1=$1-$3*
$2=$2-$3*
1,1,$3X2,0-$1,0-$2*
1,1,$3X2,0-$1,$2*
1,1,$3X2,$1,$2*
1,1,$3X2,$1,0-$2*
%

Тут пришлось вспомнить реализованный когда-то на С++ парсер математических выражений, основанный на переводе в постфиксную форму и алгоритме сортировочной станции.

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

interface GraphicsObject {
   fun draw(canvas: Canvas, penConfig: PenConfig)
}

По ходу выполнения программы эти объекты дойдут до самого метода onDraw()  в View. Они либо рисуют на канве графический примитив (прямая, дуга), либо добавляют готовый Path (все flash операции, контуры в регионах реализованы с помощью Path), либо меняют настройки канвы (поворот, начало координат) или пера (цвет, размер, способ заливки).

App модуль. Все остальное осталось в апп модуле, т.е. весь UI, View Model, Repository и DI.

В Ui все стандартно. На экране со списком — RecyclerView с DiffUtils и Floating button для добавления файлов. На экране с графикой — кастомная View, в которую сеттится набор графических объектов для отрисовки. Все данные экраны получают из вью моделей, которые в свою очередь берут данные из центрального репозитория. Интерфейс репозитория:

interface GerberRepository {
   val gerbers: List
   suspend fun addItem(fileUri: Uri, fileName: String): GerberResult
   fun removeItem(id: String)
   fun changeItemVisibility(id: String, visibility: Boolean)
}

Cхема взаимодействия экранов, view model’ей и репозитория:

Что в итоге

После нескольких месяцев работы вечерами после работы и по выходным проект был завершен. Выглядит это все вот так:

Часть функционала по спецификации Gerber осталась не реализована, т.к. он либо не влияет на изображение, либо я не встречал его в реальных файлах за 9 лет работы конструктором и пока отказался от реализации (а время немного поджимало, т.к. перспектива выгореть из-за пет проекта на самом старте профессии — ну такое :) ). В репозитории я отметил все эти моменты в виде небольшой roadmap в readme.

Большое спасибо тем, кто дочитал статью до конца, с радостью отвечу на вопросы в комментариях. Приветствую конструктивную критику. До встречи!

Ссылка на репозиторий в Github

Ссылка на GooglePlay

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


  1. slvno
    22.12.2021 20:08
    +2

    Классный проект. splenectomy, вы реализовали часть подсистемы для демонстрации фотолитографического процесса :) Если еще в отображение на экране добавить естественные цвета для меди, свинца, и других элементов из которых состоит слой, а также процент заполнения (типа время экспозиции для каждого элемента), то вообще красота будет подключай планшетник к технологической установке и печатай что угодно, хоть платы, хоть процессоры, хоть 3д модели.


    1. forthuser
      22.12.2021 20:44
      +2

      Одно из применений такого CNC
      Лазерная установка для засветки фоторезиста от AlphaCrow
      Программа на стороне ПК, уже сделанa в разных вариантах есть и с использованием C#, JavaScript браузерного исполнения, связь с контроллером установки через USB или Uart, но может быть и беспроводное решение. Для привязки к реперным точкам экспонируемой платы используется Веб камера или предустановленные штифты.

      P.S. Лазерный меч в руках на аватарке автора проекта имеет ещё и дополнительный смысл контекста его трактования (кроме контекста использования лазера установки HLDI), а именно, что авторская программа для ПК сделана на Forth (Форт) — отсылка к ассоциации изображений мастера Йоды. ????


  1. vkni
    22.12.2021 20:56
    +1

    Я думал, что ваш Gerber хотя бы есть среди https://github.com/antlr/grammars-v4 и можно было бы не писать собственный парсер, а взять antlr4, но увы, он слишком редкий.

    И, если, как вы говорите, разработчики САПР не выдерживают спецификацию, а ведь там есть даже PEG грамматика - https://www.ucamco.com/files/downloads/file_en/415/the-gerber-parsing-expression-grammar_en.ebnf?79d8bb116dd0168c5b920615a89297a7

    Т.е. построить валидатор по ней - дело пары часов.


    1. splenectomy Автор
      22.12.2021 22:14

      Спасибо за интерес к статье. С PEG дела не имел, спасибо за наводку, нужно будет разобраться :) Помимо проверки валидности файла я все же хотел дать шанс САПРам, чтобы можно было открывать их файлы (Mentor Graphics например), несмотря на несоблюдение стандарта.


  1. quaer
    23.12.2021 00:30
    +1

    Хотел попробовать, но не получилось:

    • На голом андроиде невозможно выбрать файл, он просто не видит гербер файлы видимо из-за их расширения. Проблема решилась установкой Total Commander. Видимо вам надо указать это.

    • Попытка открыть файл закончилась неудачей:

      "Can't process gerber!

      There is no valid G-code gerber command but 'G' was found"

    • Исключение в коде при запуске сторонней активити тоже надо обработать :)


    1. splenectomy Автор
      23.12.2021 01:10

      Спасибо за фидбэк) Не совсем понял, почему андроид не видит, и чем помог тотал? У меня чистый андроид 11 на эмуляторе и miui на телефоне, проблем с видимостью файлов не было, главное не ставить фильтры типа Документы и т .д.

      Почему конкретный гербер не открылся - интересно на него взглянуть конечно :)

      Про исключение тоже не понял, можно уточнить?


      1. quaer
        23.12.2021 01:39
        +1

        openFileResult.launch может кинуть исключение

        Делал так: в каталог Download закинул папку с гербер файлами. В отрывшемся диалоге открыл левую шторку, выбрал там Download, открыл каталог с герберами. Файлы не виделись. Возможно, перетыкав все кнопки и можно было их как-то увидеть, но сходу не получилось. Если поставить TC, то выбрав его как программу для открытия, файлы находятся без проблем.


        1. splenectomy Автор
          23.12.2021 08:43

          если речь о файлах .gbr, то к расширению я не привязывался, т.к. часто герберы называют как попало :)
          про исключение понял. сами в коде нашли или крашнулось?


          1. quaer
            23.12.2021 12:56
            +1

            Окно программ разок неожиданное пропало при тыканиях при попытке выбрать файл.

            В коде попробовал исправить параметры Intent чтобы можно было выбрать файл, не получилось. Ну и увидел, что у вызова нет обёртки.


  1. srg27y
    23.12.2021 07:33

    альтиум вьювер не требует лицензии и позволяет открывать герберы.


    1. splenectomy Автор
      23.12.2021 07:58

      когда я писал еще десктопное приложение (в самом начале статьи я писал об этом и о лицензии) нужен был скорее не вьювер, а автоматизированный конвертер в картинки (который обрезает их сразу по контуру + оставляет отступ, дает файлам названия сам). так то и в mentor graphics была возможность смотреть герберы, но она была сильно всратая и без нормального экспорта. а приложение под андроид - это уже другое :)

      з.ы. компания, которая спецификацию гербер разрабатывает, тоже запилила свой онлайн вьювер средней всратости у себя на сайте)


      1. quaer
        23.12.2021 12:57

        Есть же прекрасный бесплатный 3d гербер вьювер.


  1. nixtonixto
    23.12.2021 13:48
    +1

    Как-то странно он показывает:

    Здесь слои меди и трафарета, трафарет по факту не отображается. Заливка полигонов тоже не видна, и апертуры зачем-то размытые. Хотелось бы видеть плату вот так:

    В том виде, как сейчас - лично я использовать не могу. Зазоры трафарета от апертур не видны, заливка не видна, изображение не имеет фотографический вид. Порадовало, что вы подсвечиваете узлы - этого мне не хватает во вьюверах и самой САПР. И хотелось бы, чтобы программа могла открывать зип-архив с файлами, как это сделано, например, у Резонита.


    1. splenectomy Автор
      23.12.2021 19:49

      с апертурами очень прикольно у вас получилось, если не возражаете, приду за гербером :), а заливка отображается же. просто изображение полупрозрачное, и "узлы" видны по этой же причине. нужно будет добавить настройку прозрачности, чтобы можно было ее настроить/отключить.