Как я устал ждать и написал* свой STEP-экспортёр для Allegro
Как я дошёл до жизни такой
Уже почти двадцать лет я работаю в Cadence Allegro. Прошёл путь от ручного вбивания артикулов компонентов и лейаута с выводными детальками до полноценной git-версионируемой базы компонентов, кросс-проверок с инженерами-конструкторами и формирования документации почти автоматом. И всегда меня мучил один момент: из оркада, кейденса, аллегро — называйте этот ECAD как хотите — никогда нельзя было получить адекватную трёхмерку для обмена с конструкторами. Но вот наконец появилось оно: технологическое нечто, которое позволило мне не погружаться в изучение внутреннего языка SKILL, не изучать питон, а просто сказать ему: мне нужно вот это и чтобы оно работало вот так, делай! И оно сделало. Это небольшая история о том, как инженер получил доступ к современной нейросети и во что в итоге это вылилось.
Для ленивых: навайбкодил тул на питоне и SKILL, качайте https://github.com/karlsonrwa/Simple3D, читайте ридми, заводите issue если что-то работает не как ожидалось, постараюсь поправить, но не могу обещать, что сделаю это быстро.
Немного лирики
Я начинал ещё с 15.7. Там не было трёхмерных моделей, а в альтиуме они уже тогда были, ЕМНИП. Но путь был труден: у нас на кафедре все работали в оркаде, поэтому сомневаться было нельзя. Помню, как меня убило решение, которое я где-то вычитал: если хотите примерно неплохие трёхмерки, то можно понаставить плейсбаундов друг на друга, каждому такому плейсбаунду задать свою высоту и получить примерное приближение корпуса компонента.
Б — боль.

Это был шок. Конечно, таким мазохизмом я не занимался. Я просто старался пилить хорошие платы. Пока я учился и работал в институте, разработчики кейденса разродились наконец-то относительно адекватным трёхмерным редактором внутри оркада. Это был прорыв! Можно было размещать нормальные модели компонентов на их посадочных местах и чувствовать себя крутым инженером, который не просто баундинг-боксами отдаёт конструктору плату с детальками, а показывает, как конкретно выглядела бы его плата в натуре. К сожалению, не помню точно, в каком из релизов завезли такой функционал. В 16.6, кажется, но могу ошибаться. А дальше всё завертелось. Постепенно в оркад вкорячили новый 3D с новым маппингом компонентов, потом добавили 3Dx, в котором свои настройки, потом убили экспорт простого STEP. И вот тут…
А как надо?
С каждым новым проектом приходило понимание о том, что модели должны быть:
-
Покрашенные, так нагляднее всем причастным.
-
Простые, потому что не у всех вокруг есть топовые компьютеры, которые легко переваривают платы в сотню мегабайт.
-
Максимально точные, потому что когда проектируешь механику с зазорами по 0,1–0,5 мм, то очень важно учесть все технологические нюансы.
С первым пунктом всё очевидно и относительно просто. Используем генератор моделей, или честно тырим из интернета, или рисуем свои. Делим грани при необходимости, красим, объединяем в одно тело.
Со вторым выяснился любопытный факт. Не все конвертеры в STEP одинаково полезны. Наиболее компактные по объёму файлы получаются у конвертера из Autodesk Inventor. Солидворкс делает более тяжёлые файлы, к примеру. Но это история про отдельный тул: как только я его допилю до удобоваримого вида, возможно, напишу и о нём тоже.
Саму же плату долгое время я экспортировал через родной простой экспорт в STEP. В нём было всё хорошо, кроме невозможности задать цвет платы заранее. Для красивости всегда приходилось открывать модель, назначать цвет плате, пересохранять. И ещё однажды выяснилось, что гибкую плату можно экспортировать в простом виде только без сгибов, плоским листом, что сводило на нет всю затею с передачей такого тела конструктору. Нет, конечно, конструкторы молодцы, они получали такую модель и гнули её средствами своего САПР, но всё равно это боль-печаль. Особенно если обмен моделями происходит хотя бы раз в день.
Чтобы не мучить коллег, я стал делать им экспорт не простой, а из 3D и позже из 3Dx. Но проблема всех этих экспортов одна и та же: безумное количество граней.
Дабы не быть голословным, возьму плату из моей статьи про брелок и давайте посмотрим, какого объёма будут файлы при разном экспорте и что мы потеряем или приобретём.

Параметры вывода вида при этом такие.

Шейпы оставлены специально, иначе шелкография потеряется. Да, на плате есть teardrops, от которых остались шейпы рядом с падами, но они не сильно добавляют веса по сравнению со всем остальным.
Сделаем теперь экспорт платы в STEP в таком виде. Получится файл объёмом 10 281 654 байт. И тут мы встречаемся с первой проблемой. Конструкторам вообще не нужны площадки для пайки у компонентов и соответствующие вскрытия в маске. Ну ок, уберём их (уберу pins из included objects) и посмотрим на трёхмерку снова.

И что же мы видим? А сквозные пины тоже пропали. Нет деления на поверхностные и сквозные пины в фильтре этого инструмента. Что делать, ведь сквозные отверстия бывают механическими? Если таких мест на плате мало, можно просто в подклассе Board Geometry → Cutout понаставить окружностей, равных диаметру сверла. И именно так я долгое время и делал, когда пользовался выводом степов из инструмента 3D.

Файл на диске при этом занимает всего 5 489 272 байт (без шелкографии становится 3 556 230 байт). Уже намного лучше, но проблема с отверстиями, как бы корректнее сказать, подбешивает. Потому что забыл добавить катаут — получи вопрос от конструктора: а куда делось? Плюс это ужасно негибко.
Если что, напомню, что бывают платы с кучей отверстий (привет всяким шляпам на малины и прочему подобному), где всё же хотелось бы видеть нормальные отверстия под компоненты, например. Да, понятно, что можно раскопировать катауты массивом. Можно, а зачем?
Ненадолго отвлечёмся на другую тему, а именно: толщина
Из чего складывается финишная толщина платы? Всё относительно просто — рассмотрим на примере обычной двухслойки.
У нас есть диэлектрик определённой толщины. Будем ориентироваться на цифры от Резонита. В случае платы в 1 мм толщина непосредственно ядра будет 0,964 мм. Далее с двух сторон у него медная фольга после гальваники. То есть у нас на выходе из производства не 18 мкм базовой фольги, а, например, все 45 мкм. Поверх меди наносится паяльная маска толщиной от 25 мкм (имейте в виду: это параметр AABUS, привет настройщикам линии распыления маски на живых проектах, — бывают сюрпризы).
Ещё есть шелкография — примем её толщину за 25 мкм — и паяльная паста. Вот с последней всё неоднозначно. Высота подъёма компонента на пасте сильно зависит как от геометрии площадки пайки, так и, собственно, от объёма нанесённой пасты, который зависит от технологии монтажа. Для упрощения примем этот параметр за 25 мкм. То есть фактически будем считать, что если опорная плоскость выводов корпуса элемента находится заподлицо с нижней гранью его основного тела, то компонент как бы лежит этой гранью на маске. В оркаде стек платы выглядит примерно так.

Здесь, очевидно, используются номинальные значения толщин без допусков. Для хорошего приближения к реальности, на мой взгляд, этого вполне достаточно.
К чему я заговорил о толщине? А к тому, что давайте посмотрим, что нам выдал родной 3D.

1,104 мм при выводе меди и маски, компоненты лежат на маске, но слои (ядро, медь, маска, шелкография) выведены отдельными телами.

Те же 1,104 мм, если убирать медь из вывода, и между ядром платы и маской просто воздух.
А вот что будет, если полностью убрать маски из экспорта.

Толщина ядра, разумеется, сохраняется, но шелкография и компоненты висят в воздухе. Неподготовленных конструкторов это немного пугает. И всё равно приходится красить тело такой платы. Мелочь, но напрягает. Потому что приходится это делать с каждой платой.
И это мы ещё не посмотрели, какой треш выводится при экспорте гибких плат с криво определённым стеком, когда производство говорит, что ему не нужны наши файлы адгезива, маски, покровной плёнки и прочего правильного и корректного.
Я не буду утомлять читателя такими же экспериментами с экспортом из 3Dx. Там разве что модели удобнее обновляются, всё обмазано свистоперделками, ортографический вид не запоминается между вызовами инструмента, но, к счастью, хотя бы маразмом с катаутами заниматься не приходится, потому что разработчики таки додумались разделить отверстия, пины и прочие элементы на более мелкие группы. Но накликивать в каждой новой плате правильные параметры вывода — то ещё удовольствие.
При этом из-за разъединённых тел в виде масок, ядер и прочих неубираемых элементов размер файла разрастается просто катастрофически.
На не самых свежих машинах построение сечений таких плат превращается в изощрённую пытку. Хотя кому как. Некоторые люди, которых я знаю, просто ставили такую плату на импорт в свой САПР и уходили на полчасика пить чай.
— Ну а чо, я работаю, оно импортируется.
С выходом аллегро версии 25.1 оказалось, что теперь и обычный 3D устарел. Да, вот так. Любимое занятие разработчиков из кейденса: делаем инструмент, не доводим его до ума, в следующей версии заменяем на другой с похожим функционалом со своими ортогональными глюками. Код пишется, лавешка мутится, что ещё менеджерам надо?
Вы мне скажете: позвольте, любезнейший, есть же экспорт-импорт через IDF или IDX, автоматизация там всякая. Я вам на это скажу: офигеть, теперь мне две базы вести вместо одной. Это хорошо, когда в компании есть библиотекарь. Когда его нет, кто должен всё сопровождать и чинить взаимные глюки между ECAD и MCAD? Вот то-то и оно. А разрабатывать когда?
Разумеется, есть ещё MCADX (больше мостов и надстроек богу надстроек!). Я пытался его подружить с солидом. Вполне вероятно, что у меня кривые руки. Но скорее это всё работает в какой-то рафинированной чётко выверенной среде.

В общем, сидел я на работе и периодически грустил, когда делал очередные трёхмерки, а конструкторы мучились с висящими в воздухе компонентами.
Ещё небольшое отвлечение в сторону
Есть такой замечательный проект InteractiveHtmlBom. Он изначально заточен на кикад, изиеда и есть экспорт из альтиума. Я уже не помню, как именно наткнулся на репозиторий некоего juulsA в гитхабе, где на том самом ужасном Lisp-подобном SKILL был написан рабочий экспортёр из оркада в промежуточный JSON, который может быть втянут в iBOM.
juulsA если вы читаете эту статью, хоть и в переводе, огромное вам человеческое спасибо! Своим экспортёром вы сэкономили не одному коллективу разработчиков тысячи человекочасов тупой офмительской работы!

Так вот, полез я посмотреть, что там нового товарищ понаделал, и увидел, что он написал простенький экспортёр в STEP через JSON аж в прошлом году. Видимо, тоже был разочарован выпиливанием привычного функционала. И так удачно совпало, что понадобилось мне оформить подписку на Claude для совершенно других задач и даже не для себя изначально. Кто ж знал, что меня так затянет. Я начал с модификации имеющихся вспомогательных SKILL-скриптов, а продолжил написанием целого тула для нормального экспорта.
Что сделано
Почему получилось как получилось? Я знаю основы программирования, могу написать что-то для STM32, читаю (с трудом) питон, SKILL и ещё многое-многое околожелезное. Но сам написать полноценный скрипт на SKILL я точно не осилю (скиллов не хватает). Мой максимум — написать свою ветку меню для оркада. Я долго и безуспешно в разные периоды времени пытался убедить своего коллегу Михаила помочь со скриптами к аллегро, но его тоже можно понять: когда ни разу не щупал лисп и он тебе вообще не нужен, тем более такой узкоспециализированный вариант, то зачем тратить на это жизненное время? А на работе, разумеется, никогда не было возможности выделить время и бюджет на то, чтобы заняться работой с неизвестным результатом. Бизнес такое не очень любит. Поковыряв немного экспортёр juulsA’а я понял, что он даёт неправильную итоговую толщину печатной платы и не позволяет экспортировать гибкие платы, которых становится всё больше и больше в проектах.
И тут я подумал, а что мне мешает скормить репозиторий Клоду и поковыряться в нём, авось чего да получится?
В общей сложности я работаю над инструментом уже почти месяц. Очень активно в первые две недели. Сейчас потихоньку допиливаю корявые места по отзывам коллег. Не буду утверждать, что получилось идеально, в коде наверняка куча багов, глюков и чего-то ещё, но в целом работает и выдаёт намного более легкие трёхмерки, что, собственно, и было изначальной целью.
Для примера, обсуждаемая плата похудела до 2 663 203 байт и при этом выглядит так:

Вас может смутить шелкография выполненная поверхностью, но на это есть отдельная ручка, как выражается Клод: можно шелк вывести телом, а можно поверхностью. Очевидно, что в случае поверхности размер файла будет меньше, потому что граней меньше. С точки зрения проецирования на чертёж, что инвентор, что компас, что солид одинаково успешно с этим справились. А шёлк ведь иногда бывает нужен в оформлении чертежей.
Компоненты при этом, разумеется, лежат на маске и плата имеет полную проектную толщину:

Безусловно, такой экспорт не учитывает технологический разброс параметров при производстве печатной платы. Но это уже на совести конструктора.
Самое «интересное» взаимодействие началось, когда я решил, что надо не повторить, а превзойти и сформулировал задачу по экспорту гибких и мультистэковых плат. На это ушло больше всего тестирования, правок, обсуждений.
В итоге теперь можно строить вот такую прелесть:

При желании можно также вывести любую плату с раздельными слоями и посмотреть, правильно ли сделан стек. Медь, разумеется, выводится упрощенно по всей площади платы. У меня не было цели сделать полный экспорт вообще всего. Для этого есть родной 3Dx, кхе-кхе.

Нейронический текст с описанием полезных особенностей тула
Следующий текст полностью написан Клодом по актуальной на момент публикации статьи версии инструмента и описывает использованные решения и особенности.
Что внутри и почему сделано так
Инструмент разрезан пополам
SKILL умеет читать базу аллегро и не умеет строить твердотельную геометрию. Ни B-rep, ни булевых операций, ни STEP на выходе там нет и не предвидится. Геометрическое ядро OpenCASCADE умеет ровно обратное: строит что угодно, но про аллегро не знает ничего.
Поэтому инструмент состоит из двух частей. SKILL читает плату и пишет промежуточный JSON. Питон читает JSON и строит STEP. Граница проходит по файлу, а не по вызову функции, и это удобно ещё по одной причине: когда результат выглядит странно, у меня на руках остаётся текст, который можно открыть и прочитать.
Из этого выросло правило, которым я пользовался весь проект: спорные вопросы решаются экспортом с реальной платы, а не чтением документации. Формулировки в мануале SKILL часто допускают два прочтения, и выбирать между ними наугад — дорогое занятие.
Построитель написан на питоне
У juulsA построитель сделан на C++ поверх того же OpenCASCADE. Чтобы им пользоваться, нужен собранный бинарник и библиотеки рядом с ним. Мне нужно было другое: отдать инструмент коллеге, у которого нет ни компилятора, ни желания выяснять, почему не находится очередная DLL.
Поэтому построитель переписан на питон. Ядро то же самое, ставится одной командой:
pip install cadquery-ocp
Это весь requirements.txt, одна строка. Правда, строка увесистая: пакет тянет VTK жёсткой зависимостью, и втроём они занимают около 470 МБ. Взамен инструкция по установке умещается в три пункта.
Работать можно и без аллегро вообще — окно запускается само по себе, а для пакетной сборки есть командная строка:
python -m stepbuilder STEP_DIR JSON_DIR OUTPUT_DIR —batch
Толщина берётся из стека, а не из головы
Тело платы — это диэлектрики, полигоны, проводники и обе паяльные маски. Шелкография и паста исключены: они нанесены на плату, а не являются ею. Для двухслойки получается
1.464 (диэлектрик) + 0.045 + 0.045 (медь) + 0.025 + 0.025 (маска) = 1.604 мм
и это число каждый раз собирается из стека конкретной платы, а не берётся из константы в коде.
На мультистэкапной плате толщина каждой зоны берётся из собственной цифры аллегро на стекап, а не суммируется по именам слоёв. Причина простая: у флекс-стекапа слоя SOLDERMASK может не быть вовсе — на его месте покровная плёнка, клей и стиффенер, — и сумма по именам молча посчитала бы всё это нулём.
Есть галочка «не включать маску»: она убирает маску и смыкает стек к ядру ровно на снятую толщину, каждую сторону отдельно. Плата при этом действительно становится тоньше, и это тот случай, когда результат стоит проверить самому.
Отдельно — переключатель, где находится z = 0, сверху или снизу платы. Детали стоят на паяльной маске своей стороны: реальные площадки несут припой, который поднимает деталь до уровня маски.
Как собрано тело платы
Тут три режима, и они есть на любой плате, не только на гибко-жёсткой: у обычной платы зон нет, поэтому её контур становится одной неявной зоной на единственном стекапе.
-
Solid — одно тело. Самый компактный вариант, для примерки в корпус нужен именно он.
-
Solid colored layers — тоже одно тело, но границы слоёв сохранены, и торец показывает стек. Примерно в 4,7 раза больше по объёму файла.
-
Not stitched — каждый слой каждой зоны отдельной деталью. Нужно, когда плату хочется разобрать глазами.
Торец платы красится отдельным цветом, но своё тело одного цвета есть только у Solid, поэтому в двух других режимах этот элемент управления гаснет, а не игнорируется молча. Мелочь, но неработающий на вид рабочий контрол — это вопрос, который потом придёт ко мне.
Что попадает в экспорт
Базовое правило: все символы с позиционным обозначением плюс любой символ, несущий STEP-модель без обозначения — кронштейн или корпус разъёма, поставленный прямо на плату. Дальше два исключения, и их порядок важен.
NO_STEP_EXPORT сильнее всего остального. Свойство вешается на компонент или на его определение, и деталь не попадёт в модель, даже если вариант её устанавливает. Каждый такой символ называется в консоли.
Список варианта решает за всё, у чего есть позиционное обозначение. Обозначение — это ровно то, чем деталь можно назвать в Variants.lst, поэтому механика с обозначением подчиняется списку наравне с любым компонентом: нет в списке — значит список говорит «не установлена». Символ без обозначения находится вне системы вариантов и экспортируется во всех: аллегро оставляет обозначение пустым, когда связанного компонента нет, и назвать такую деталь в списке, сгенерированном из схемы, просто нечем.
ALWAYS_STEP_EXPORT — выход из этого правила. Деталь с таким свойством остаётся во всех вариантах, что бы ни говорил список. NO_STEP_EXPORT по-прежнему сильнее, потому что «никогда» побеждает «всегда».
Свойство появилось из-за случая, который по данным не разрешается. Площадка под пайку провода и корпус разъёма в базе выглядят одинаково: обозначение есть, STEP-модель есть, строки в BOM нет, ни в одном варианте не названы. Но корпус должен исчезать вместе со своим разъёмом, а площадка — часть голой платы и нужна в каждом варианте. На чертеже платы без шелкографии именно эти площадки монтажнику и надо видеть. Разница здесь в намерении, а намерение можно только записать на детали.
В отличие от штатного NO_STEP_EXPORT, этого свойства не существует, пока его кто-нибудь не заведёт, а пока не заведено — прицепить его нечем. Поэтому simple3d.il заводит его как пользовательское свойство типа BOOLEAN и дальше не вмешивается: на что вешать — решает пользователь, через Edit → Properties. Из этого следует несколько вещей, которые стоит знать:
-
Словарь свойств принадлежит проекту, а не установке, поэтому запись создаётся на каждую плату — по триггеру открытия и ещё раз в начале экспорта. При загрузке самих SKILL-файлов не происходит ничего: версия, которая писала в базу при загрузке, совпала с падениями аллегро при старте.
-
Заведение меняет плату, и аллегро попросит её сохранить. Кому это мешает — ставит defineAlwaysExportProp в false, и экспорт продолжает читать свойство там, где оно уже заведено.
-
У BOOLEAN-свойства нет значения: оно есть или его нет. Чтобы снять пометку, свойство надо удалить, а не выставлять в false.
Повешенное на определение компонента, оно закрывает все экземпляры библиотечной детали сразу — пометил площадку в библиотеке, и платы править не пришлось.
Вся плата, без учёта вариантов. Когда Variants.lst есть, экспорт дополнительно пишет <плата>.json — все компоненты, кроме помеченных NO_STEP_EXPORT, и список вариантов из них ничего не вычитает. Чертежу иногда нужно показать то, что есть на голой плате, а не то, что ставится в конкретной сборке. Файл помечен изнутри («full_board»: true), потому что отличать его от <плата>_<вариант>.json по имени — это гадание: вариант может называться как угодно.
Сам Variants.lst читается из папки, где лежит .brd, и больше нигде — там его держит аллегро. Если файла там нет, консоль называет проверенный путь. Два вида неподходящего файла отвергаются, а не экспортируются молча: заглушка с пустым списком (она не установила бы ничего) и файл от другого проекта (обозначений много, но ни одного с этой платы). В нормальном случае печатается покрытие, вида variant list covers 47 of 51 placed component(s).
И заодно решается боль с катаутами из первой части. Отверстия попадают в модель потому, что они есть на плате, а не потому, что я вспомнил нарисовать окружность в Board Geometry → Cutout.
Размер файла и структура сборки
Дерево получается такое:
<имя_платы> ├── PCB_<плата> одно тело итоговой толщины ├── silkscreen_top_<плата> шелкография сверху ├── silkscreen_bot_<плата> шелкография снизу ├── symbols_top_<плата> компоненты верхней стороны │ ├── cap_D8x10mm │ └── cap_D8x10mm та же деталь ещё раз └── symbols_bot_<плата>
Одна деталь на каждую уникальную STEP-модель, названа по имени файла. Десять одинаковых резисторов стоят одного тела, а не десяти. Вхождения лежат прямо в двух группах, без обёртки на каждое позиционное обозначение.
Имя платы несёт каждый узел верхнего уровня, а не просто PCB и symbols_top. Это нужно, когда конструктор импортирует несколько плат в одну сессию: иначе деталь или группа одной платы подменяет другую.
Каждая сторона шелкографии — отдельная деталь, её можно скрыть или перекрасить, не трогая плату.
Есть галочка Compact STEP: она убирает параметрические кривые на поверхностях и даёт примерно вдвое меньший файл при той же геометрии. Чего она не уменьшит — так это моделей компонентов: их B-rep приходит из библиотеки, и всё, что лежит внутри них, остаётся как есть. На плотной плате основной вес — именно там.
Шелкография
Строится настоящей геометрией: залитые области, либо выдавленные в тонкие тела на грани платы, либо нарисованные плоскими поверхностями чуть выше неё.
Ширины, глифы и кривые берутся у аллегро. Линия шелкографии — это осевая плюс ширина, и превращением этого в залитый контур занимается axlPolyFromDB, с предварительной векторизацией текста через axlText2Lines. Ничего не обводится и не смещается вручную, поэтому в STEP попадает та же геометрия, что уходит в гербер. Каждый полигон сверяется с площадью, которую сообщил аллегро: кривая, восстановленная не в ту сторону, молча не пройдёт.
Дальше два решения, которые выглядят противоречиво, но замерены:
-
Поверхности объединяются обязательно. Копланарные грани на одном z мерцают друг об друга там, где штрихи перекрываются.
-
Тела не объединяются намеренно. Булева операция над тысячами тонких пересекающихся призм стоит времени решателя и делает файл больше — измерено, 154 %, — не давая ничего видимого.
Поверхности примерно вчетверо компактнее тел, поэтому для передачи конструктору имеет смысл именно этот режим. Плата за это одна: у краски нет толщины, и в булевых операциях она не участвует.
Какие слои аллегро вообще собираются, задаётся в конфиге. А какие из собранных дойдут до модели — решается на каждую сборку в окне, и список строится по JSON, поэтому предлагаются только слои, давшие геометрию на этой плате, с числом полигонов, которое каждый стоит. Снял галочку, нажал Generate снова — повторный экспорт из аллегро не нужен. В конфиге при этом хранятся исключения, а не включения, поэтому слой, впервые появившийся на плате, рисуется, а не пропадает молча.
Линия без ширины или текст с нулевым пером не могут быть отпечатаны — рисовать нечем и самой фотошаблонной программе, — поэтому объект пропускается и называется вместе со слоем и координатами. И в консоли аллегро, и в логе окна: консоль обычно уже прокручена к моменту, когда смотришь на модель.
Ещё одна вещь, до которой я дошёл не сразу: легенда одинакова для всех вариантов сборки. Текстолит изготавливается один раз на все варианты, поэтому маркировка неустановленного компонента физически на плате есть, и собирается она один раз на проект.
Цвет
Цвет платы выбирается в окне до построения, а не назначается потом руками в механическом САПРе. Берётся он из тем самого аллегро, по умолчанию Dark_green. Торец — отдельным цветом: по нему глаз опознаёт плату быстрее всего.
Цвета слоёв для режима Solid colored layers по умолчанию взяты из собственных цветов материалов аллегро — медь #B87333, база #FCFFD6, покровная плёнка #F29440 и так далее. Смысл в том, чтобы экспорт выглядел так же, как та же плата в 3D-канвасе, и конструктору не приходилось привыкать к новой палитре.
Отдельная настройка — negativeLayers: слои, чьи нарисованные фигуры являются окнами, а не материалом. Покровная плёнка, маска и паста рисуются так по соглашению, а стиффенер, клей и эпоксид — наоборот. Если тела какого-то слоя получились инвертированными, слой дописывается в этот список, а не правится код.
Гибко-жёсткие платы
Rigid-flex — это несколько зон, у каждой свой стек и своя толщина. Экспорт читает их из проекта и собирает плату как эти зоны, слитые в одно тело.
Зоны выравниваются по меди, а не по внешним граням. Зона стиффенера 2,44 мм и зона флекса 0,365 мм имеют общее проводящее ядро, и стиффенер растёт от него наружу, в основном вверх. Выравнивание по верхним граням разорвало бы плату на каждой границе зон. Компоненты при этом стоят на своей зоне, поэтому деталь на стиффенере и деталь на флексе разнесены по Z на два миллиметра — и это правильно, именно так они и стоят в реальности.
Сгиб в аллегро — не геометрия: это линия на RIGID FLEX/BEND_LINE, область на RIGID FLEX/BEND_AREA и свойство с углом, внутренней стороной и порядком. Экспорт читает все три и складывает модель.
Всё двигается вместе. Плата, легенда и компоненты сначала размещаются плоско, а потом переносятся сгибом, поэтому деталь не может съехать с поверхности, на которую её поставили. Компонент, оказавшийся в области сгиба, размещается на дуге и отмечается в логе: это нарушение правил проектирования, а не выбор модели.
Какая сторона двигается — якорь. Кусок, содержащий начало координат, остаётся в плоскости XY, а всё за каждым сгибом поворачивается от него. У аллегро есть та же идея, Setup → Anchor 3D View, но в 24.1 запрошенная точка до файла платы не доходит, поэтому проект сообщить её не может и соглашением служит [0, 0]. Якорь решает не только положение, но и форму: если он в середине, два хвоста отгибаются от удерживаемого центра, а если с краю — те же два сгиба дают цепочку.
Поверхности сгибов — истинные цилиндры. Там, где плата поперёк области сгиба одинакова, сечение вращается вокруг оси; иначе на цилиндр переносится сам контур. Гранится дольками по 7,5° только та форма, к которой не подошло ни одно точное построение, и лог называет сгиб и причину. Плоские панели по обе стороны не аппроксимируются никогда.
Сгиб растягивает материал снаружи нейтральной поверхности и сжимает изнутри, и модель это показывает: объём каждого слоя выходит умноженным на отношение его радиуса к нейтральному.
Про K-фактор. Сколько плоского материала съедает сгиб — это длина дуги по нейтральной оси, угол × (радиус + k × толщина), где k по умолчанию 0,5: физически там нейтральная ось симметричного флекса и находится.
А вот аллегро рисует свои области сгиба по внутренней дуге, угол × радиус, без слагаемого с толщиной вообще. Измерено на трёх реальных платах, каждый раз с точностью до десятой доли микрона. Это то же самое, что сказать: развёртка аллегро построена при k = 0.
На плате с запасом разница не видна. Она проявляется, как только две области сгиба соприкасаются. Флекс, свёрнутый в замкнутое кольцо, — два сгиба по 180°, чьи области стоят в одной десятитысячной миллиметра друг от друга, — при k = 0 смыкается с точностью до полумикрона, а при k = 0,5 каждому сгибу не хватает 0,3 мм материала. Для такой платы foldNeutral ставится в 0.
Что нужно из двух — зависит от того, воспроизводите вы разводку конструктора или моделируете материал. Когда два сгиба действительно претендуют на один материал, экспорт называет оба, оставляет второй плоским, складывает всё остальное и говорит, какой foldNeutral их бы примирил.
Окно и сборка
Сборка идёт в дочернем процессе. OpenCASCADE умеет не выбросить исключение, а умереть, и на сложной плате булева операция иногда именно это и делает. В потоке это закрыло бы окно, не записав никуда ничего; в отдельном процессе окно выживает, сообщает код возврата и подсказывает, что обычно помогает — Not stitched, которая ничего не сшивает, или более грубый угол дольки при сгибе.
Пока идёт сборка, остальные элементы окна погашены. Настройки к этому моменту уже сняты снимком, и правка на ходу лишь выглядела бы действием. Кнопка Generate при этом становится Cancel, и отмена убивает сборку немедленно: с булевой операцией, которая уже минуту сидит внутри OCCT, иначе не получится. Файл, который писался в этот момент, может остаться недописанным, и лог об этом говорит прямо.
Лог раскрашен по важности: оранжевый — предупреждения, тёмно-красный — ошибки, зелёный — успех. Прогресс охватывает всю сборку, а строка рядом называет этап.
Модели компонентов
Папки с моделями задаются списком, по одной в строке, и просматриваются по порядку: побеждает первая, где нашёлся файл. Поэтому папка проекта, поставленная выше общей библиотеки, перекрывает отдельные модели, не ломая библиотеку. Каждая папка обходится рекурсивно, а имя, найденное дважды, называется в логе вместе с победившим путём.
Регистр имени значения не имеет. Имя приходит из таблицы сопоставления аллегро, где его набирают руками, а файл на диске назван поставщиком библиотеки, так что MODEL.STEP и model.step — один и тот же файл, ровно как и для самой Windows. Точное совпадение при этом всегда в приоритете.
Отдельная вещь, на которую стоит обратить внимание: аллегро хранит собственную копию каждой привязанной модели внутри .brd, а Simple 3D этими копиями не пользуется — он собирает из файлов на диске. Поэтому плата может выглядеть полной в 3D-виде аллегро, а в экспорте компонента не окажется. Случаи различаются, потому что чинятся по-разному: модели нет на диске, но она есть в плате (вытащить из 3DX-канваса и положить в любую папку из списка); нет нигде (брать там, где лежит библиотека); есть, но не читается — занята другим приложением, нулевая после сорвавшегося копирования, диалект, который OpenCASCADE не принимает. В последнем случае называются причина и путь, компонент пропускается, а остальная плата собирается.
Настройки: два файла, и ваш из них только один
Отслеживаемый simple3d_config.json содержит поставляемые умолчания. Рядом с ним simple3d_config.local.json содержит то, что отличается именно у этой установки, — прежде всего папки с моделями. При чтении они сливаются ключ за ключом, локальный побеждает, и окно пишет только локальный файл и только то, что отличается от умолчания.
Это и делает обновление безопасным. Оно не может ни конфликтовать с вашими путями, ни затереть их; улучшенные умолчания по-прежнему доходят; абсолютные пути и положение окна не попадают в коммиты. Создавать локальный файл руками не нужно — окно напишет его при первом закрытии, а удаление ключа из него возвращает поставляемое значение.
И одно жёсткое правило: если любой из двух файлов не читается — его нет или он отредактирован в невалидный JSON — не записывается ничего до конца сессии, даже если починить файл при открытом окне. Поля на экране в этот момент содержат умолчания, а не ваши настройки, и запись затёрла бы только что исправленный файл. Лог при каждом старте называет оба файла.
Туда же относится решение не считать настройками то, что настройками не является. Путь к JSON и папка вывода запоминаются, когда их выбрали в окне, но экспорт, запущенный из аллегро, эти поля заполняет и не записывает: они описывают плату, а не предпочтение.
Запуск из меню
Инструмент запускается пунктом File → Export → Simple 3D и не требует батника рядом. В конфиге можно выбрать pythonw вместо python — тогда окно открывается без консоли.
Командная строка при этом собирается по нескольким правилам, каждое из которых обязательно:
-
Строка не начинается с кавычки. cmd убирает первую и последнюю кавычку строки, и путь с пробелами внутри разъезжается на куски. Начинается с голого слова — start, cmd, cd, — и кавычки внутри выживают.
-
start вызывается с пустым заголовком окна и с явным указанием рабочего каталога.
-
Команды не связываются через &&. Связка привязывается к внешней оболочке, и если система обернёт строку в ещё один cmd, рабочий каталог теряется, а запуск пакета питона перестаёт разрешаться.
На это есть отдельный тест, включая отрицательный контроль: проверяется не только что правильная строка работает, но и что неправильная ломается.
Одна особенность SKILL, о которой стоит знать всем
Файлы .il загружаются один раз за сессию аллегро. Любая глобальная переменная, заполняемая по схеме «если ещё пустая — заполнить», при экспорте следующей платы всё ещё держит предыдущую. Не устаревшую копию, а именно предыдущую плату.
Поэтому все такие переменные сбрасываются в начале работы экспортёра. Отлаживать это неприятно: первый экспорт после запуска аллегро всегда правильный.
Чего инструмент не делает
Раз уж речь про инженерную задачу, ограничения стоит назвать прямо.
Фрезеровочные пути не экспортируются. Путь фрезы — это открытая осевая линия плюс диаметр инструмента, а не граница; чтобы выдавить его, пришлось бы отступить на половину диаметра в обе стороны и замкнуть, со скруглениями концов и обработкой углов. Всё, что должно стать отверстием, рисуется замкнутым контуром на BOARD GEOMETRY/CUTOUT — это граница, которую экспорт вычитает напрямую.
Сложенный сгиб — цилиндр, а не модель изгиба стека. Поверхности точные, материал растягивается и сжимается как положено, но того, что сгиб делает с медью, клеем и покрытием по отдельности, здесь нет. Для примерки, зазоров и картинки годится; расчёт напряжений во флексе это не заменяет.
Шелкография не вычитается под отверстиями. Обрезка идёт по контуру платы и вырезам, но не по сверловке. Легенду поверх отверстий всё равно не печатают, так что заметно это только если линия проведена через отверстие намеренно.
Как это проверяется без аллегро
Аллегро на машине, где пишется код, нет, а SKILL без аллегро не исполняется. Отсюда четыре слоя проверок:
-
Механические проверки SKILL — баланс скобок по каждой процедуре и соответствие числа аргументов при вызове объявлению функции. Исполнение это не заменяет, но снимает класс опечаток, которые иначе всплывут в аллегро посреди экспорта.
-
Тесты на питоне — включая регрессию по геометрии: не «не упало», а «объём тела равен вот такому числу».
-
Тесты окна. Tk на винде запускается без дисплея, поэтому GUI проверяется тестами наравне с остальным кодом.
-
Зонды — дюжина маленьких скриптов, каждый спрашивает у аллегро ровно одну вещь: как выглядят слои, что лежит в области сгиба, какие есть варианты сборки. Их запускаю я, руками, и приношу ответ.
Последний пункт, пожалуй, и есть ответ на вопрос, что делал человек, если код писала нейросеть. Человек приносил данные. Ни то, что развёртка аллегро построена при k = 0, ни то, что зоны выравниваются по меди, ни разница между площадкой под пайку провода и корпусом разъёма из документации не выводятся — это измеряется и решается на настоящей плате.
Я прекрасно понимаю, что в наше время никого вайбкодингом не удивишь, но лично для меня это оказалось очень продуктивным открытием. Особенно порадовало, что я не написал ни одной строчки кода и не сделал ни единого коммита. Я только ставил задачу, проверял результат, подсовывал документацию и примеры. Наконец-то я закрыл гештальт, который изводил меня долгие годы.
Немного статистики
Я попросил Клода подбить статистику, сколько я работал с ним над инструментом. Результаты получились весьма интересные.
Нейростатистика с подробностями как они есть
1. Токены
|
категория |
токенов |
|---|---|
|
свежий ввод (не из кэша) |
36 806 |
|
запись кэша (cache write) |
25 331 491 |
|
чтение кэша (cache read) |
1 760 794 155 |
|
вывод (генерация модели) |
4 112 711 |
|
ВСЕГО |
1 790 275 163 ≈ 1,79 млрд |
Разбивка по моделям:
|
модель |
API-вызовов |
ввод |
вывод |
запись кэша |
чтение кэша |
|---|---|---|---|---|---|
|
claude-opus-4-8 |
438 |
2 130 |
455 195 |
1 964 340 |
68 794 350 |
|
claude-opus-5 |
4 049 |
34 676 |
3 657 516 |
23 367 151 |
1 691 999 805 |
|
итого |
4 487 |
36 806 |
4 112 711 |
25 331 491 |
1 760 794 155 |
Как это честно подавать в статье
«1,79 млрд токенов» — цифра верная, но она на 98,4 % состоит из чтения кэша. Это не «модель прочитала 1,8 млрд слов»: это один и тот же контекст, который переотправляется на каждом шаге агента и тарифицируется по ставке кэш-чтения (в разы дешевле обычного ввода). Содержательные цифры:
-
4 487 обращений к модели — столько раз агент «думал».
-
4,1 млн токенов сгенерировано — это ~16 МБ текста: код, патчи, рассуждения, записи в memo. Для сравнения: репозиторий сейчас — 10 705 строк Python, 4 647 строк SKILL, 7 300 строк документации.
-
25,3 млн токенов записи кэша — реальный объём «нового» материала, прошедшего через контекст: файлы, вывод тестов, логи Allegro.
-
~428 токенов кэш-чтения на каждый токен генерации — вот настоящая цена агентного цикла, и это главный неочевидный факт для читателя Хабра.
Оговорки (обязательно упомянуть)
1. Транскрипты сохранились не полностью. Отсутствуют два куска:
-
18–19 июля — раунды 1–8 из 51: первый порт C++ → Python, ревью, отключение MFRPN. 5 коммитов 19 июля.
-
22 июля — целый рабочий день, раунды 10 и 10a–10j (вся шелкография, включая четыре раунда на знак дуги). 13 коммитов — один из самых плотных дней проекта.
То есть все цифры — нижняя граница. На непокрытые дни приходится 18 коммитов из 100, так что реальные величины выше примерно на пятую часть: скорее ~49 часов и ~2,1 млрд токенов. В статье честнее приводить измеренное, а рядом строкой сказать про пробел — заголовок «два миллиарда» при этом остаётся верным с обеих сторон.
2. Два транскрипта — форки одной и той же сессии (c12cb5c8 дублирует be78da64, 96e4d88e дублирует часть 87b1a21b). Токены дедуплицированы по requestId, поэтому дважды не посчитаны; счётчики промптов и вызовов инструментов приведены с исключением этих двух файлов.
3. ~7 млн токенов (0,4 %) — это не разработка, а сессия написания самой статьи (b73ed5e4, 2–7 августа). На порядок величин не влияет, но если захочется быть совсем точным — вычитается.
2. Время
|
метрика |
значение |
|---|---|
|
первое зафиксированное событие |
20.07.2026 22:44 |
|
последнее |
07.08.2026 22:40 |
|
календарный срок |
18 дней (с 18.07 — 20 дней) |
|
активное время у клавиатуры |
~41 ч |
|
дней с реальной работой |
17 |
|
промптов от человека |
236 |
|
ответов модели |
7 673 |
|
вызовов инструментов |
4 506 |
|
коммитов (мои, без upstream) |
100 из 120 |
|
раундов в memo |
51+ |
«Активное время» = сумма интервалов между соседними событиями транскрипта, если пауза ≤ 15 мин. Оценка чувствительна к порогу, поэтому вот весь диапазон:
|
порог паузы |
активное время |
|---|---|
|
2 мин |
25,4 ч |
|
5 мин |
30,0 ч |
|
10 мин |
36,6 ч |
|
15 мин |
41,0 ч |
|
30 мин |
49,1 ч |
|
60 мин |
63,9 ч |
Формулировка для статьи: «около сорока часов чистой работы за три недели», диапазон 30–49 ч в зависимости от того, что считать паузой.
По дням
|
день |
активно, ч |
токенов |
|---|---|---|
|
20.07 |
1,3 |
14 907 237 |
|
21.07 |
0,8 |
11 355 272 |
|
22.07 |
— |
нет транскрипта (13 коммитов) |
|
23.07 |
0,2 |
3 787 171 |
|
24.07 |
1,7 |
43 113 202 |
|
25.07 |
5,2 |
227 582 839 |
|
26.07 |
6,5 |
346 088 434 |
|
27.07 |
5,1 |
217 405 475 |
|
29.07 |
0,5 |
14 031 136 |
|
30.07 |
2,9 |
106 241 251 |
|
31.07 |
4,2 |
237 313 179 |
|
01.08 |
3,9 |
137 806 664 |
|
02.08 |
3,3 |
192 401 420 |
|
03.08 |
0,7 |
17 868 999 |
|
04.08 |
2,8 |
124 899 875 |
|
05.08 |
0,7 |
52 695 051 |
|
06.08 |
0,2 |
680 435 |
|
07.08 |
0,9 |
42 097 523 |
Пик — 25–27 июля: rigid-flex, послойная сборка платы и сгибы. Три дня из семнадцати съели 44 % всех токенов. Августовский хвост (3–7 августа) — доводка: варианты сборки, локальный файл настроек, поведение окна во время сборки.
По сессиям
|
сессия |
начало |
конец |
промптов |
токенов |
|---|---|---|---|---|
|
85a485a4 |
20.07 22:44 |
21.07 11:09 |
20 |
26 262 509 |
|
f25902b1 |
23.07 09:05 |
02.08 19:56 |
39 |
297 027 521 |
|
9bd584a6 |
24.07 21:03 |
26.07 14:20 |
53 |
332 730 371 |
|
26d25ab9 |
26.07 15:01 |
27.07 00:39 |
17 |
330 495 966 |
|
be78da64 |
27.07 10:50 |
27.07 18:44 |
17 |
142 703 004 |
|
43aa4725 |
27.07 18:53 |
01.08 11:50 |
48 |
412 467 980 |
|
87b1a21b |
02.08 21:09 |
02.08 21:47 |
6 |
5 789 886 |
|
b8daf090 |
02.08 21:48 |
07.08 12:43 |
28 |
221 370 377 |
|
b73ed5e4 |
02.08 22:53 |
07.08 22:40 |
6 |
6 911 545 |
|
8dae2201 |
07.08 21:57 |
07.08 22:19 |
2 |
14 516 004 |
Обратите внимание: 17 промптов = 330 млн токенов (сессия 26d25ab9, раунды про сгибы). И ещё сильнее — 2 промпта = 14,5 млн (8dae2201). Один вопрос вида «почему обе стороны сгиба не сходятся» разворачивается в сотни шагов агента.
3. Что получилось (для раздела «результат»)
|
|
|
|---|---|
|
Python |
10 705 строк, пакет stepbuilder/ |
|
SKILL |
4 647 строк: makeVariant3dIntermediates.il + simple3d.il |
|
Тесты |
18 файлов tests/test_*.py + 2 механических чекера SKILL |
|
Документация |
README 1 120 строк (двуязычный), PROJECT_NOTES 5 469, CHANGELOG 493, QUICKSTART 239 |
|
Зонды в Allegro |
tools/probes/*.il |
|
Побочные репозитории |
3 (step2html, 3dproperties, checkBase) — выделились из этого проекта |
4. Чего в цифрах нет
-
Стоимость в деньгах не считалась: работа шла по подписке, а не по API-тарифу. Если нужна оценка «во сколько это обошлось бы по API», её надо считать отдельно по актуальному прайсу.
-
Не учтено время на проверку в самом Allegro: экспорт реальной платы, открытие STEP в SolidWorks/Inventor, разглядывание геометрии. Это часы поверх сорока, но они не попадают в транскрипт.
-
Не учтены раунды 1–8 (18–19 июля) и весь день 22 июля.



Много это или мало? Не знаю, если у вас есть примеры похожих несложных проектов, было бы любопытно сравнить.
Конец?
Ещё год назад какое-то прикладное использование нейросетей в моей области профессиональной деятельности представлялось баловством. Ведь для обучения нужны многие и многие данные, тесты, разметка, вот это всё из чего состоят современные нейросети. А теперь оказалось, что при правильном подходе даже сейчас можно не просто задавать глупые вопросы и читать такие же ответы, а реально использовать инструмент, пока что во благо. Видимо не за горами тот день, когда и профессия инженера электронщика изменится кардинальным образом, как уже местами меняются многие другие.
Я долгое время был скептиком и с усмешкой смотрел на происходящее вокруг, думая, что меня-то уж это не коснется. Но теперь я в этом так сильно как раньше не уверен. Допускаю, что это всего лишь вау-эффект от первого реально полезного лично для меня использования нейронки и скоро это пройдёт, но кто знает.
Что же всех нас ждет дальше? Хм, время покажет, как всегда.
Спасибо, что дочитали статью!
P.S.: отдельная огромная благодарность Дмитрию, Артёму, Сергею, Максиму, @Flammmable и Galim за тестирование и критику.
Источник: habr.com
Похожие записи
Оцените материал:
Присоединяйтесь и подпишитесь на рассылку самых свежих новостей по Email
Получайте свежие новости и идеи на почту. Без спама — только самое интересное.
Нажимая «Подписаться», вы соглашаетесь с политикой конфиденциальности.
