[Перевод] KiCad: скругление дорожек и создание каплевидных падов
KiCad с годами стал намного лучше, но при этом ему по-прежнему недостаёт возможности рисовать плавные, закруглённые дорожки с каплевидными падами (teardrops). И хоть многие находят этот функционал не особо нужным, в его пользу существует ряд аргументов, которые и привели к реализации данного проекта, а именно двух плагинов — для скругления дорожек и формирования каплевидных подводов.
Предыстория и мотивы
Так выглядели многие ранние, рисованные от руки печатные платы, каждая дорожка на которых была плавной и слегка изогнутой:
Из PDP-8
Старый осциллограф
Плата управления памятью ядра Data General Nova 3 (из chookfest)
Сдвиговый регистр из оборудования космической программы Apollo
Когда люди впервые начали использовать для проектирования печатных плат компьютеры, вариантов разводки было очень мало, и дорожки неизбежно получались с острыми углами. Некоторые специалисты забывают, что наша склонность ограничивать дорожки углами 450 является пережитком того времени, когда по-другому было просто никак.
Некоторые преимущества каплевидных падов и скруглённых дорожек оспариваются, но я приведу наиболее общепринятые:
- изогнутые углы потенциально уменьшают вероятность ошибиться при травлении;
- в случае несоосности просверлённого отверстия и пада каплевидный подвод снижает вероятность прорыва дорожки;
- в гибких печатных платах острые углы концентрируют напряжение, поэтому их устранение снижает вероятность перелома дорожки;
- использование произвольных углов позволяет делать более плотную разводку;
- также может наблюдаться повышение электромагнитной совместимости (ЭМС), поскольку острые переходы создают резкое изменение импеданса.
На последнем пункте люди обычно останавливаются отдельно. Опыты показывают, что на частотах ниже Ггц разница оказывается незначительной. Поэтому многие разработчики утверждают, что ввиду отсутствия преимуществ от ЭМС смысла в подобном подходе нет. Но меня реально задевает мнение людей в духе «Только невежды, которые не разбираются в EMC, станут стремиться к закруглённым дорожкам».
Здесь я хочу указать на два важных момента:
- каплевидные пады и закруглённые дорожки круто смотрятся. Некоторые делают их такими даже просто ради красоты;
- отсутствие поддержки функционала, который реализован в других программах CAD, вызывает отток пользователей. К примеру, для проектировки гибких плат я не использую KiCad.
Многие пробовали добавить этот недостающий функционал в исходный код KiCad, но по разным причинам наиболее перспективные ветки так и не дошли до этапа слияния. Возможность скругления дорожек, по всей видимости, заложена в проект KiCad 6, выход которого, как я надеюсь, уже не за горами. (прим. пер.: к сожалению, в KiCad 6 этот функционал так и не добавили, но он обещается в седьмой версии).
Ещё этот функционал можно добавить через «экшен-плагины», то есть скрипты Python. Такой способ намного ограниченнее, но зато исключает необходимость рекомпиляции исходного кода KiCad.
Он не позволяет создавать изогнутые дорожки интерактивно, но у меня возникла идея: рисовать плату с острыми углами, после чего скруглять их через плагин. Изгибы можно аппроксимировать с помощью множества мелких прямых сегментов, и при сохранении исходного файла их всегда можно будет скорректировать, после чего применить скругление в качестве дополнительного шага, генерирующего готовую схему в новом файле.
Существующие решения
На деле оказалось, что я не единственный, кто решил пойти таким путём, и мне попался вот этот репозиторий, где подобная попытка уже реализована. Сработал предложенный скрипт отлично!
Я нашёл ещё один репозиторий, который переупаковывает предыдущий скрипт как экшен-плагин, хотя на сборках Windows он не работает. Ввиду ужасной цепочки зависимостей и проблем релизы KiCad для Windows статически упакованы с помощью Python 2 и старого wsWidgets API. Проблема эта незначительна и касается только диалогового окна GUI.
Тем не менее обе версии скрипта имеют ещё один проблемный нюанс, который, на первый взгляд, может быть незаметен.
Алгоритм поэтапно скругляет углы, отсекая дорожки при каждой итерации, если их угол превышает установленное пороговое значение. Точка отсечения определяется либо параметром максимальной длины, либо процентным отношением этой длины к длине текущей дорожки — выбирается наименьший вариант. Однако алгоритм не учитывает изменение длины, происходящее непосредственно в процессе закругления — в результате чего оно никогда не получается симметричным.
В некоторых топологиях это не имеет значения, но когда на плате присутствует шина очень тесно расположенных дорожек, оказывается катастрофическим (что бывает очень часто). По сути, дорожки обрабатываются в случайном порядке, поэтому неравные изгибы вызывают множество ошибок DRC (проверка правил проектирования).
Обратите внимание, что две дорожки были обработаны в обратном направлении, и неравные изгибы нарушают зазоры. Можно, конечно, просто раздвинуть дорожки, но нет никакой причины, по которой бы скрипт не мог выполнять ровное закругление, сохраняя одинаковые зазоры.
Корректировка подразделения
Эта операция скругления, по сути, представляет собой двухмерную версию Subdivision Surface (подразделения поверхности), которую можно наглядно продемонстрировать в Blender (с помощью Catmull-Clark):
При этом стоит помнить, что разделение применяется равномерно, здесь нет никаких параметров, кроме количества итераций. Тот факт, что экшен-плагин запрашивает два дополнительных неясных параметра (length %
и scaling %
) уже настораживает. Нам нужно ограничить максимальный радиус изгиба, но для этого требуется всего один параметр.
Если порыться в исходном коде, то выясняется, что length %
— это величина укорачивания, определяемая кратчайшей дорожкой на пересечении. Разобраться с этим было непросто, но предустановленное значение 50% означает, что проходов требуется вдвое больше.
Чтобы разобрать весь процесс подробно, рассмотрите одну L-образную дорожку. Если дорожки будут иметь бесконечную длину, то первое разделение должно будет определить, где их разрезать, и именно здесь может пригодиться параметр радиуса.
Следующий этап скругления должен подрезать новые углы, а значит, будет логично, установить точку соединения примерно на четверти пути вдоль текущего среза.
Третий проход обрезает уже эти углы и так далее. Заметьте, что для получения гладкого изгиба каждый сегмент должен иметь одинаковый размер.
В скругляющем скрипте при установке length %
на четверть сегменты будут получаться не совсем одной длины. Но это всё ещё не объясняет странные результаты, которые мы наблюдали. На деле основная проблема скрипта в том, что после срезания угла длина сегмента изменяется, и для следующего угла используется неверное значение.
Сохранение исходных длин сегментов в словаре при каждом проходе должно исправить проблему. Наконец, изгибы ожидаемо получаются плавными, и мы уже не получаем на шине кучу ошибок DRC.
Тем не менее результат меня всё ещё не удовлетворял. Чтобы обеспечить идеальное скругление, нужно добавить фактор косинуса. Я порисовал на бумаге треугольники и придумал новую формулу для высчитывания процента длины сегмента. Подставив её в скрипт, мы, в конце концов, проходим самый сложный тест — квадрат. Если скругление будет работать корректно, то он должен превращаться в идеальный круг.
Использование равных сегментов обеспечивает максимальную плавность изгиба, достижимую за заданное число итераций. По моим наблюдениям обычно достаточно трёх. Ничто не мешает использовать больше, но если сегменты становятся короче ширины дорожки, то это теряет смысл. KiCad начинает тормозить при обработке тысяч сегментов, и ошибки DRC возникают многократно при каждом нарушении.
Тем не менее обновлённая программа разделения асимптотически приближается к фиксированному изгибу, и гарантированно сохраняет не менее одного сегмента от каждой входящей дорожки. Чтобы обнаружить и исправить большую часть проблем с DRC достаточно двух проходов, после чего для финальной топологии можно проделать дополнительные.
Превосходно. А теперь самое сложное…
Каплевидные подводы
В репозитории flexRoundingSuite реализована некоторая поддержка каплевидных подводов, но недостаточно качественная. В ещё одном репозитории предлагается подход получше с использованием заливки зон. Это даёт отличное преимущество в виде полного отсутствия ошибок DRC, поскольку, если нет места для подвода, то медная заливка пада автоматически отступит.
Однако в моём случае корректно сгенерировалось всего несколько подводов. Алгоритм предполагает, что каждая дорожка подходит к паду или отверстию прямо, никогда не отклоняясь от оси, что оказывается верным лишь в некоторых случаях. Опять же, можно пройтись по печатной плате и подправить их вручную, но меня такой вариант не устраивает.
Очень некачественный подвод. В идеале каждый пад должен выглядеть, будто его окунули в мёд — решение, отвечающее условию наименьшей энергии, бесконечно дифференцируемое. То есть рёбра многоугольника в местах соединения пада и дорожки должны располагаться по касательной.
Для этого я добавил в плагин использование кубических кривых Безье. У кубической кривой есть четыре точки: начало, конец и две контрольных. Если мы обеспечим, чтобы и первая, и вторая контрольные точки, которые соединяются с отверстием и дорожкой соответственно, были касательными, то кривая всегда будет переходить в них плавно. Остаётся разобраться, какой «вес» нужно установить для каждой из этих двух точек.
Подводы регулируются двумя параметрами, по сути, длиной и шириной, но даже при сохранении этих значений фиксированными изменение веса может привести к тому, что подвод станет из жирного тонким.
Чтобы этот вопрос прояснить, я нарисовал в Photoshop кривые и поэкспериментировал с ними.
Поигравшись с различными вариантами, я определил веса как функции параметров размеров. Над расшифровкой экшеш-плагина пришлось поломать голову, но в конечном итоге я добился генерации весьма неплохих форм.
Если ширина подвода меньше 100%, точку сопряжения необходимо немного повернуть. На скриншоте ниже всё выглядит не сильно печально, но оставлять это так не стоит.
Чтобы всё сработало как надо, мне пришлось переписать алгоритм вычисления длины подвода, найдя пересечение дорожки с периметром отверстия. Для этого наверняка есть элегантный математический способ, но поскольку расстояние очень мало, я использовал простейший метод подбора.
На крайнем левом изображении подвода показана короткая дорожка, подходящая к отверстию под углом. Пришлось немало поэкспериментировать, чтобы подобрать веса Безье, подходящие под все ситуации. Важно сказать, что когда дорожка смещена относительно центра, вес с одной стороны должен отличаться от веса с другой.
Совмещение скриптов
Несмотря на то что подводы теперь получаются красивые, они не дружат с закругляющим дорожки плагином. Дело в том, что если к паду подходит кривая, то рассматривается только первый её сегмент, и в лучшем случае генерируется очень маленький подвод.
Здесь мне представилось два варианта решения. Первый — это отредактировать скрипт, чтобы не сегментировать дорожки, расположенные слишком близко к паду. Второй — это отредактировать плагин генерации подводов, чтобы он считывал ситуацию, следовал по дорожке вдоль её изгиба и начинал формировать подвод с дальней стороны назад. И хоть первый вариант выглядит проще, чаще всего мы имеем ситуацию с небольшим изгибом 450 сразу перед отверстием, и если его не закруглить, то подвод должен будет принять очень странную форму. Так что я склонился ко второму решению.
На этом этапе я, пожалуй, выскажусь по поводу написания скриптов на Python для этих экшен-плагинов. Я уже говорил, что здесь используется Python 2 для Windows и Python 3 для Linux. Плагины кэшируются, поэтому любое внесение изменений влечёт за собой перезапуск KiCad. Печально то, что большинство сообщений об ошибках сбрасываются в /dev/null, но если их перехватить, то они наверняка окажутся неверными, так как их информация будет относиться к не кэшированным файлам Python. Мне кажется, здесь должно быть решение (простое удаление файлов .pyc не помогло), но я его так и не нашёл.
В Python есть консоль, которая позволяет reload()
импортируемый скрипт без необходимости перезапускать приложение. Скрипт генерации подводов специально писался так, чтобы его можно было вызвать либо из этой консоли, либо как экшен-плагин, что очень удобно, если сравнивать со скриптом для скругления дорожек.
Как бы то ни было, но больше всего я недоволен слабой документацией. Есть немного справочного материала по doxygen, но он не имеет прямого отношения к представленному Python API. Похоже, что лучше всего искать помощи в чужих экшен-плагинах. Майлс МакКу, который упоминается в исходном коде нескольких плагинов, ведёт блог, где можно найти много полезной информации.
С учётом всего сказанного, фактический алгоритм для обратного прохода вдоль сопряжённой дорожки оказывается не таким уж страшным.
Если сопряжённая дорожка короче, чем целевая длина, тогда мы ищем дорожки с тем же узлом, слоем и начальной или конечной точкой. Каждая из них перебирается, пока не будет достигнута нужная длина, или не закончатся дорожки. Конечная позиция используется в качестве вектора для начала формирования подвода.
Как видно на картинке выше, это не даёт идеального результата для произвольных, нереалистичных углов, но при использовании разумных или скруглённых дорожек итог получается именно таким, на какой я рассчитывал.
Поигравшись с параметрами, я получил другие результаты, но все они оказались достаточно удовлетворительными. Если вам нужны реально жирные капллевидные пады, то теперь они возможны!
Есть здесь и пограничный случай — когда при следовании по дорожке встречается несколько путей. В ситуации с T-перекрёстком, закруглённом в гладкую Y, следование только по одной дорожке приводит к формированию подводов странной формы. Поскольку удачного способа исправить это нет, то при встрече на дорожке развилки код у неё останавливается и помещает в этом месте небольшой подвод. Так результат получается более-менее сносный.
Естественно, размещение отверстия на дорожке ведёт к формированию подводов с двух сторон. Чтобы это точно произошло, можете дорожку в этом месте разорвать.
Заключение
Попользовавшись какое-то время плагином для закругления, я всё больше склоняюсь к применению именно этого подхода либо использованию «исходного» файла топологии с острыми углами дорожек и обработанного готового макета платы уже в изящном виде. Даже в программах CAD, поддерживающих изогнутые дорожки, сглаживание обычно применяется в конце, так как, в противном случае, перемещение элементов по схеме вызывает боль.
В качестве желательного функционала здесь сильно недостаёт возможности делать изгибы автоматически соответствующими требованиям DRC. После исправления логики разделения тесно расположенные дорожки больше не выбрасывают идиотских ошибок, хотя все ещё сохраняются проблемы, которые необходимо корректировать, если исходная схема платы выполнена некорректно. Идеальным решением здесь будет обнаруживать ошибки DRC по мере их возникновения, как это делает режим маршрутизации highlight collisions, и выбирать подходящий радиус изгиба.
Это подразумевает либо прямой подход (обработка сначала внутренних дорожек), либо более сложный итеративный способ, поскольку в случае шины или одинаковых дорожек только самую внутреннюю можно изогнуть, не вызвав изначально ошибку DRC. В случае разделения будет весьма проблематичным сделать сначала небольшой изгиб, а когда освободится больше места, вернуться и этот изгиб увеличить.
Похоже, что Python API выступает здесь ограничивающим фактором, так как ни одна из систем DRC недоступна. Но я думаю, что алгоритм должен действовать примерно так:
- создавать структуру из всех подлежащих скруглению пересечений;
- для каждого из них записывать максимально возможное закругление на основе длины дорожки и расстояний до фиксированных точек, таких как отверстия и пады;
- для каждого искать все пересечения на внутреннем радиусе при максимальном закруглении и создавать список дорожек, которые необходимо обработать перед текущей;
- упорядочивать структуру согласно зависимости от дорожек;
- прорабатывать структуру, закругляя пересечения, не имеющие зависимостей, и удаляя эти пересечения от всех элементов, которые от них зависят. Продолжать по списку, по ходу проверяя ошибки DRC;
- Повторить процесс n раз.
Представленный скрипт отлично работает в своём текущем виде. При этом у меня есть ещё кое-какие идеи по его доработке. Было бы здорово иметь возможность выбирать группу дорожек и разделять только их. Также была бы не лишней возможность ограничивать минимальную величину угла между дорожками. Это бы устанавливало точку завершения, когда выполнение дополнительных проходов уже не имеет эффекта. Появилась бы реальная возможность корректировки уже обработанного файла — если вы захотите изменить дорожку, нарисовать новую, удалить изгиб, то при повторном запуске скрипт повлияет только на это.
Ну и ещё одна потенциальная доработка: я сказал, что при использовании зон для подводов никогда не возникают ошибки DRC, что не совсем так. Если два подвода наложатся друг на друга, то это вызовет проблему. Доработка, позволяющая смещать подводы от центра, повышает такую вероятность. Исправить это можно с помощью инкрементирования или рандомизирования приоритета их зон, чтобы одна всегда шла первой. Такой приём исключит (или усложнит) опцию remove all teardrops, поскольку приоритет зон используется как ID, но при рендеринге в новый файл с закруглением дорожек это проблемой не станет.
Как использовать плагины
Мои доработки в плагин для формирования подводов уже добавлены в репозиторий kicad_scripts.
А вот со скриптом для скругления дорожек сложилась странная ситуация. Похоже, что каждый участник проекта создал новый репозиторий без привязки к истории коммитов. Получился бардак, но поскольку я также планирую вносить в скрипт дополнительные доработки, то просто продолжу их традицию и тоже создам свежий репозиторий. Я решил соединить историю коммитов двух других репозиториев, чтобы хоть немного всё подчистить, хотя там по-прежнему недостаёт начального периода. Сам плагин лежит здесь.
На следующей неделе ожидайте продолжение темы.