Чтобы нейросеть отдавала не «инструкцию, как нарисовать», а готовый файл, сначала пришлось понять, что такое .ms14 изнутри.
Оказалось, что это история про алгоритм сжатия из восьмидесятых, капризный XML и программу, которая никогда не объясняет, почему не открывает файл.
Снаружи: заголовок и чанки
Файл начинается с 36-байтной ASCII-строки MSMCompressedElectronicsWorkbenchXML, затем два 32-битных числа: полный размер XML и ноль.
Дальше идут чанки: размер распакованных данных (до 900 000 байт), размер сжатых и сами данные.
"MSMCompressedElectronicsWorkbenchXML" 36 байт uint32 размер XML uint32 0 чанки: uint32 распакованный | uint32 сжатый | данные
Сжатие: PKWARE DCL implode
Данные сжаты алгоритмом PKWARE Data Compression Library — implode с ASCII-литералами и словарём 4 КБ. Распаковщик — порт классического
blast.c Марка Адлера. А вот со сжатием вышла первая ловушка: поток из одних литералов формально корректен, распаковщик его читает,
но Multisim такой файл молча не открывает. Пришлось написать полноценный implode с поиском совпадений. Чтобы он был быстрым на Python,
поиск идёт через встроенный bytes.rfind, а коды Хаффмана развёрнуты заранее.
Внутри: XML с сюрпризом
Под сжатием — XML. Вторая ловушка: в значениях атрибутов встречаются настоящие переводы строк. Любой XML-парсер по стандарту их нормализует,
и после пересохранения Multisim снова молча отказывается открывать файл. Решение — перед разбором заменить переводы строк внутри атрибутов
на , а при записи вернуть обратно. Пересериализация оригинала после этого даёт файл байт в байт.
Строки Multisim хранит в двух видах: &ASC… для ASCII и &UNI… с шестнадцатеричными вставками для Юникода.
Графика (символы, провода, точки) и электрика (компоненты, порты, узлы) лежат в разных ветках документа и связаны числовыми ID.
Детали: клонировать, а не синтезировать
Первая версия собирала элементы «синтезом»: брала резистор и подменяла SPICE-шаблон, модель и рисунок. Конденсатор так получился — но Multisim показывал его ёмкость в омах: подпись номинала строится по коду типа, а код остался от резистора.
Правильный путь — переносить элемент целиком из файла, сохранённого самим Multisim: символ, SPICE-модель, запись базы, окно свойств.
Такой элемент неотличим от поставленного руками. Источники — примеры, которые ставятся с Multisim, и сам Multisim: в его интерфейсе
автоматизации есть ReplaceComponent, которым можно заменить заготовку в шаблоне любой деталью базы и сохранить файл.
Ловушки, найденные только в Multisim
- Переименование проекта. Безобидная правка имени внутри файла — и он перестаёт открываться. Имя листа остаётся от образца.
- Одинаковые GUID. Два файла, собранные на одной основе, имели одинаковые GUID. Открытые одновременно, они путались: мультиметр останавливал моделирование, показания замирали. Теперь все GUID новые и зависят от имени файла — сборка остаётся повторяемой.
- «This circuit has no power sources». КМОП-инвертор в покое не потребляет ток, и Multisim не может сам выбрать шаг моделирования. Помогает записать шаг TMAX = 10 мкс прямо в файл.
- Прыгающий ключ. У переключателя есть кадры анимации, их нужно поворачивать вместе с символом — иначе при нажатии ключ прыгает на старое место.
- Перевёрнутая надпись. У интерактивного источника есть отдельная надпись с показанием; если вращать её как графику, она зеркалится.
Проверка без Multisim
Нетлист для ngspice собирается из того же XML, который увидит Multisim, а шаблоны деталей разворачиваются по тем же правилам.
Две находки: модели ОУ Multisim пишут ток как 1.516e-005A — Multisim букву «A» игнорирует, а ngspice читает её как «атто» (10⁻¹⁸);
мультиметр Multisim — это вольтметр на 1 ГОм, и без такой нагрузки разомкнутый выход в ngspice «висит». После этих правок для схемы SN74LS00
ngspice дал 0,530 В, а мультиметр в Multisim — 529,947 мВ.
Итог
Формат .ms14 прост, пока его читаешь, и коварен, когда пишешь: почти каждая ошибка проявляется одинаково — файл просто не открывается.
Всё найденное собрано в reference.md проекта.