Здравствуйте, уважаемые хаброжители.
Сегодня я хочу поделиться небольшой наработкой, призванной конвертировать PCAD-овские карты сверления в G-код. Гибко, просто и open-source. Правда, прости-осспади, на Qt. Писать на нем, конечно, приятно, но вот деплоить и собирать чужие коды…
Часть первая. Механика.
Некоторое время назад я отложил свой проект по голове для приуса, и вот для чего:
Пока ждал микросхемы, пока эксперементировал со схемами, отчетливо понял, что я хочу изготавливать печатные платы дома. Да-да, есть опыт лазерно-утюжной технологии, и даже рисования лаком, но хотелось чего-то настоящего. Решено было использовать пленочный фоторезист и УФ-лампу для ногтей. Естественно, встала проблема сверления и металлизации отверстий. А получается она оттуда, что отверстия нужно металлизировать до того, как на плате протравятся дорожки. Иначе подать ток к каждому отверстию — целая история.
Получается, что ручное сверление отпадает, т.к. ориентироваться просто не по чем (бумажки с картами отверстий давайте не будем предлагать).
Решено было навесить на имеющийся 3D-принтер вместо Direct-головы — шпиндель, и так и жить. И вот тут появились решения, которыми я хотел бы сегодня поделиться.
Сменная головка для RepRap
Для того, чтобы можно было легко превращать принтер в сверлилку и обратно, решено было сделать головку разъемной. Бегунок, прикрученный к ремням — отдельно, а все остальное — съемное. Учитывая, что это не бог весть, какая сложность, отдельной статьей выкладывать смысла нет. Тут просто фото и ссылки на STL-модели, если кто захочет себе точно такую же. В архиве так же присутствуют SLDPRT-исходники, если что подправить. Качается медленно — спасибо ADSL от белтелекома, но лежать должно долго.
Результат получился вот таким:
Головка-шпиндель
Тут все просто — после длительных попыток создать свой шпиндель, решил прикупить оный на AliExpress, и просто повесить на кронштейне. Фото нет, пока в процессе.
Генератор G-Code
А вот тут начинается самое интересное.
С присущим мне глобализмом я пробежался по имеющимся решениям, и понял, что каждое из них способно не только создать кучу проблем при разворачивании технологии дома, но и доставлять их регулярно и методично, вплоть до пенсии. Что не нравилось? Негибкость. Все они больше под станки, с предопределенными характеристиками шаблонов, и т.п. Да, дело не сложное. Но очень не хотелось однажды столкнуться с ситуацией, когда нужно чуть видоизменить алгоритм, и не иметь возможности это сделать. К примеру, не встречал тулзу, способную повернуть отверстия вокруг оси. А ведь после металлизации плату 1:1 не уложишь. Но это мысли на будущее. Пока мне это не нужно. Но уже можно. В целом, хотелось чего-то простого, легкого, гибкого и… работоспособного. Решил накропать самостоятельно.
В качестве базы были использованы библиотеки Qt 5.11. Приложение написано в консольном стиле. Архитектура приложения выполнена в linux-стиле.
На вход приложению подается файл DRL, выдернутый из PCAD при создании Geber-комплекта. (возможно, придется доработать парсер, если захочется скормить ему что-нибудь из AltiumDesigner. Но лично я для себя решил снести этого Альтиум-монстра от греха подальше. За что теперь он является в страшных снах, и не дает забыть собственное имя)
В качестве параметра указывается файл XML. Описанию формата этого файла будет посвящана вторая половина статьи. Этот файл, по сути, определяет механизм формирования G-Code (а на самом деле — любого текстового файла) для передачи его (G-кода) 3D-принтеру.
Механизм работы приложения
1. Читается и распознается формат DRL (который М48 или Excellon). В результате получаются инструменты, содержащие список дырок, которые этими инструментами сверлятся.
2. С полученными из п.1 данными мы идем в XML, ищем там ноду script, и попросту исполняем все, что там написано. Есть пяток операторов, а большего нам и не нужно.
3. В процессе исполнения п.2 случались операторы print. Результат печатается на выходной поток.
Часть вторая. Формат XML-файла
Для того, чтобы сделать программу максимально гибкой, была использована библиотека ScriptEngine. Сам чуточку ошалел от того, что теперь реально можно сделать при конфигурации. Основной постулат таков: есть много вычисляемых параметров, работа с которыми ведется максимально прозрачно: текст передается модулю ScriptEngine, и используется результат. Та же ситуация происходит, если в шаблоне G-Code встретится комбинация ${бла-бла-бла}. При этом все, что внутри фигурных скобок, будет передано на вычисление, а весь шаблон заменен результатом.
<xml>
<variables>
<var name="ZChangeToolValue" value="30"/>
<var name="ZTravelValue" value="10"/>
<var name="ZDrillValue" value="0"/>
</variables>
<functions>
<!--
predefined function with single parameter:
a - source (requested) diameter
returns - suggested tool diameter for give requested after halvanic
if function nod defined, it assumed return=a
-->
<plate_increase_dia f="a+0.2"/>
</functions>
<tools>
<!--
"tool" node defines a real drill tool for make a hole
Depends on your technical process you can set up different
tools for plated or not holes or join same holes in single tool.
Required parameters for tool are:
1. range_min,range_max - diameters range to assign holes for this tool
You can joun different diameters (f.ex. 0.31-0.4) to single tool
2. plated="yes|no|both" - defines plated property to
Other parameters are optional and can be used later in G-Code patterns.
For example, you can define tool position or toolbox coords for the tool.
-->
<tool description="0,3mm" range_min="0" range_max="0.3" plated="both" position="0" />
<tool description="0,4mm" range_min="0.3" range_max="0.4" plated="both" position="1" />
<tool description="0,5mm" range_min="0.4" range_max="0.5" plated="both" position="2" />
<tool description="0,6mm" range_min="0.5" range_max="0.6" plated="both" position="3" />
<tool description="0,7mm" range_min="0.6" range_max="0.7" plated="both" position="4" />
<tool description="0,8mm" range_min="0.7" range_max="0.8" plated="both" position="5" />
<tool description="0,9mm" range_min="0.8" range_max="0.9" plated="both" position="6" />
<tool description="1,0mm" range_min="0.9" range_max="1.0" plated="both" position="7" />
<tool description="1,1mm" range_min="1.0" range_max="1.1" plated="both" position="8" />
<tool description="1,2mm" range_min="1.1" range_max="5" plated="both" position="9" />
</tools>
<patterns>
<!-- in any pattern you can use any variable from context where it's printing
Example (used inside 'tool' loop type):
Mnnn Please, change tool to ${description} ; message to lcd
Mnnn ; pause
Note : here ${description} is optional tag defined in <tool> node
Use this example outsite the tool loop will cause calculation error.
-->
<pattern name="start">
G90 ;${var hcnt=holesCount;var tcnt=toolsCount;"Hello"}
M117 Homing
G28 X Y
M117 Move Z to travel
G0 X${minX} Y${minY}
M76
G92 Z${ZTravelValue}
</pattern>
<pattern name="finish">
G0 Z${ZChangeToolValue}
M104 S0 ; disable spindle
G0 X0 Y220
M117 Drill finished
M300 S600 P1
; Stats:
; Holes : ${holesCount}
; Tools : ${toolsCount}
</pattern>
<pattern name="set_tool">
; Tools rest: ${tcnt--}
G0 Z${ZChangeToolValue}
G0 X100 Y0
M104 S0 ; disable spindle
M117 Change tool to ${description}
M300 S600 P1
M76 ; pause job
M117 Drilling
M104 S100 ; enable spindle
G28 X
</pattern>
<pattern name="go_drill">
; Holes rest: ${hcnt--}
; Percent rest: ${var percent=Math.round(hcnt*100/holesCount); percent}%
M73 P${100-percent}
G0 Z${ZTravelValue}
G0 X${Math.round(x*100)/100} Y${Math.round(y*100)/100}
G0 Z${ZDrillValue}
G0 Z${ZTravelValue}
</pattern>
</patterns>
<script>
<!--
"assign tools". No parameters
Just assign all tools declared in
DRL-file to tools described in <tools> node.
For each DRL-defined tool will be selected FIRST compatible
tool from <tools> node. I.e. if range 0.3..0.8 will be defined early,
<tool> node for diameter 0.4..0.5 will never be assigned.
Except 'plated' property will be different.
-->
<command verb="assign tools" />
<!--
"assign tools". No parameters
Join all DRL-file tools, assigned to same tool here
to one tool (also holes)
Just avoid multiply changing physical tool to same
-->
<command verb="join tools" />
<!--
"offset".
Offset ALL holes by defined values
xoffs, yoffs - values to offset. Before offset will be calculated
i.e. here you can use global variables.
-->
<command verb="offset" xoffs="-minX+10" yoffs="-minY+10"/>
<!--
loop for each DRL-tool (assigned and joined before).
Context inside will be filled also with tool's properties and
node's parameters
-->
<command verb="print" pattern="start"/>
<loop type="tools">
<command verb="print" pattern="set_tool"/>
<command verb="print context" line_begin=";"/>
<!--
loop for each hole inside the tool.
Context inside will be filled also with hole's properties(x&y) and
node's parameters
-->
<loop type="toolholes">
<command verb="print" pattern="go_drill"/>
<!--
"print context".
Anwhere in script you can use this verb.
It inserts all context variables available.
Usefull for debug but completely useless for
normal work
-->
<command verb="жprint context" line_begin=";"/>
</loop>
</loop>
<command verb="print" pattern="finish"/>
</script>
</xml>
На самом деле, ничего сложного нет, если вчитаться. Но давайте разберем посекционно:
<variables>
<var name="Название переменной" value="Значение переменной"/>
</variables>
В секции variables, как следует из названия, мы можем определить произвольный набор глобальных переменных. Они никак не влияют на работу программы, пока не встретятся в каком-нибудь вычисляемом выражении.
<functions>
<plate_increase_dia f="a+0.2"/>
</functions>
Функции. Ну, точнее, функция. Пока она, предопределенная, одна: вычисление реального диаметра сверла для металлизированных отверстий. Известно, что металлизация крадет диаметр, и это частенько приводит к казусам при попытке просунуть ногу компонента 0,8, которая не лезет в отверстие, заложенное, как 0,9. Чтобы не возиться с этим при проектировании я решил добавить этот функционал.
Смысл этой секции — определить функции, которые может использовать конвертер для определенных целей. Эти функции нельзя (пока?) использовать самостоятельно.
<tools>
<tool description="0,3mm" range_min="0" range_max="0.3" plated="both" position="0" />
</tools>
Сверла. Тут нужно сделать отсылку к команде скрипта «align tools», про которую ниже. Каждый элемент этой секции определяет ячейку, в которую будут собраны все инструменты, распознанные во входном файле. Идея такова, что частенько при проектировании случаются дюймовые диаметры, и множество инструментов с их значениями 0,478...0,492… и т.д. Чтобы не возиться с ними, мы задаем обязательные параметры range_min и range_max. Обязателен так же признак металлизации. Ноды XML просматриваются последовательно, и как только очередной инструмент из DRL подходит под определение — нода признается подходящей.
Можно задавать любые другие параметры в ноде. Их значение можно будет позже использовать в шаблонах.
Вы можете задать позицию в пенале или координаты, где захватить сверло, если у вас станок с автосменой инструмента. А можете описать инструмент буквами для вывода на экран принтера, если у вас, как у меня, Marlin и ручная смена сверл.
<patterns>
<pattern name="start">
G90 ;${var hcnt=holesCount;var tcnt=toolsCount;"Hello"}
M117 Homing
G28 X Y
M117 Move Z to travel
G0 X${minX} Y${minY}
M76
G92 Z${ZTravelValue}
</pattern>
А вот теперь оцените всю прелесть скрипт-машины! Шаблоны. Конвертер, как я уже говорил, работает с шаблонами просто: ищет все кусочки вида ${...}, и отправляет в скрипт-машину. А там-то JS-подобный язык. Поэтому, собственно, можно даже чуточку программировать. В данном примере можно видеть, как при выводе шаблона start мы сначала определили пару переменных, которым присвоили значения глобальных. Ну а лишь потом написали константу, которая и будет значением выполнения этого куска.
Когда этот шаблон будет выведен в выходной файл, мы увидим:
G90 ;Hello
M117 Homing
G28 X Y
M117 Move Z to travel
G0 X10 Y10
M76
G92 Z10
Ну и не могу ж не похвастаться. Оцените кусочек из шаблона для сверления каждой дырдочки:
; Holes rest: ${hcnt--}
; Percent rest: ${var percent=Math.round(hcnt*100/holesCount); percent}%
M73 P${100-percent}
да-да… каждый раз, печатая комментарий Holes rest, мы будем декрементировать значение hcnt. А она, как мы помним, была определена, пока мы печатали start, а, стало быть, находится контекстом выше. А потом будем вычислять переменную percent, чтобы после использовать ее в другом куске — при передаче ее в команду M73 (эта команда заставляет марлин подвинуть полоску прогресса). G-Код, сгенерированный этим фрагментом:
; Holes rest: 6
; Percent rest: 13%
M73 P87
кстати, toolsCount, minX — это предопределенные имена глобальных переменных.
Отмечу, что имена шаблонов не предопределены, т.е. вы можете использовать любые. Шаблон будет распечатан, когда в скрипте встретится команда print и его имя.
<script>
<command verb="assign tools" />
И основа — секция script
Внутри секции могут встречаться ноды с именами command и loop.
Формат ноды command:
<command verb="оператор действия" .... параметры для оператора ... />
Оператор действия — один из немногих операторов. Параметры для каждого описаны ниже. Могут быть дополнены любыми другими, которые, как вы уже поняли, можно использовать в шаблонах.
Формат ноды loop:
<loop type="тип цикла">
.....
</loop>
цикл — это секция, содержимое которой будет исполнено для каждого элемента, определенного типом цикла. Их два (пока):
tools — цикл выполнится для каджого инструмента, и
toolholes — цикл выполнится для каждой дырки, предназначенной для сверления этим инструментом. Очевидно, что цикл toolholes может быть только вложенным в tools.
При этом при выполнении вложенного цикла доступны все переменные для текущего инструмента. Зачем? Не знаю. Просто рассказал.
Операторы
assign tools
Параметры: нет.
Проводит присвоение каждому сверлу из исходного файла инструмента из XML. Без него большинство других действий не имеют смысла.
join tools
Параметры: нет.
Больше организационный — объединяет все инструменты, которым был присвоен один и тот же из XML-файла. Имеет смысл сразу после assign tools, но я решил дать возможность пользователю сделать свои операции.
offset
Параметры:
xoffs,yoffs — значения смещений. Работает скрипт-машина.
Смещает все отверстия на указанные значения. Да, часто так бывает, что плата разводится далеко не в начале координат.
print
Параметр:
pattern Название шаблона.
Печатает шаблон с указанным названием на выходной поток.
print context
Параметры:
line_begin, line_end — начало и конец каждой строки.
Отладочная штука — позволяет в любом месте вывести на выход все доступные в данный момент переменные и их значения. Каждая переменная выводится отдельной строкой, начало и конец указаны в параметрах
Предопределенные имена глобальных переменных.
holesCount, toolsCount — я очень, очень, очень надеюсь, что смысл этих переменных в пояснении не нуждается. Да-да. Это количество инструментов и количество отверстий.
minX, maxX, minY, maxY — и этих тоже. Нет, ну на всякий случай — это координаты поля сверления. Все дырочки находятся внутри этого прямоугольника. Пересчитывается после команды offset.
Заключение
Вот, собственно, постарался вкратце, но максимально полно описать сотворенную тулзу.
Честно говоря, пока пытался представить сценарии использования, я отчетливо представлял себе, сколько раз проявится татаро-монгольское иго на землях русских (есть мнение, что именно они принесли нам мат).
Отсюда вопрос: стоит ли заморочится, и сделать простенькую веб-страничку, куда можно вставить вход и скрипт, и получить готовый G-Code, минуя стадии сборки из исходников?
Автор: remixoff