DXF в цену: архитектура расчётного сервиса по чертежу
Оценки сроков и бюджета на сервис «клиент загружает чертёж — получает цену» чаще разваливаются не на выборе эвристики нестинга, а на стыках между слоями: один модуль передаёт другому данные без явного контракта — единицы, топология «деталь с отверстиями», правила смешивания заказов на листе, поля для КП. Три слоя — парсинг, укладка, документ — дают разные классы ошибок; интеграционная поверхность между ними дороже в отладке, чем «сердцевина» алгоритма.
Ниже — разбор по слоям на примере публично описанного процесса PrixCut (лазерная резка по DXF).
Слой 1: парсинг DXF
Что нужно извлечь
Минимально значимый результат парсинга — не «файл открылся», а:
- список замкнутых контуров под рез (внешние и внутренние отдельно);
- количество врезок (прошивок) — каждая добавляет время станка;
- единицы измерения (мм vs дюймы — встречается в файлах после конвертации из американских CAD).
DXF — не бинарный формат с жёсткой схемой. Это текстовый граф объектов. Задача парсера — построить топологию: какой контур внешний, какие отверстия к нему относятся. Без этого число врезок, длина реза и площадь уходят от факта — независимо от того, как геометрия записана в файле (LWPOLYLINE, набор LINE и ARC, SPLINE, блоки через INSERT).
Откуда берётся мусор
Реальный поток от клиентов — не sample.dxf из презентации. Типичные источники проблем:
| Источник | Что ломается |
|---|---|
| Экспорт из SolidWorks / Inventor напрямую в DXF | Сплайны, стыки, единицы, «лишняя» геометрия |
| Цепочка растр → вектор (скан, промежуточный PDF, неродной конвертер) | Потеря точности, разрывы, шум |
| Файлы из старых версий AutoCAD | Нестандартные коды объектов, двойные слои |
| Чертёж: слои «рез» и «маркировка» без конвенции | Парсер не знает, что резать, а что аннотация |
| Почти замкнутые контуры (микрозазор; порог зависит от технологии) | Для лазерной резки типичны допуски порядка сотых мм; у плазмы или фрезы пороги другие — «универсальной» цифры нет |
Правило, которое стоит зафиксировать до разработки
Какие классы файлов поддерживаем в версии 1, что возвращаем пользователю при ошибке (конкретное сообщение, а не «файл не поддерживается»), и есть ли ручной fallback.
Отладка парсера: визуализация обязательна
Без визуализации распознанных контуров отладка парсера идёт вслепую. Минимально полезная картинка: внешние контуры одним цветом, внутренние (отверстия) — другим, точки старта реза отмечены. Это не фича для конечного клиента, а инструмент разработки: без него каждый баг-репорт «файл не распознался» требует разбирать логи вместо одного взгляда на картинку.
Тест на готовность парсера
Имеет смысл прогнать 20 реальных файлов от потенциальных клиентов, не синтетику. Если больше трёх стабильно не проходят — это сигнал: либо расширять поддержку, либо явно описывать ограничения в интерфейсе.
Слой 2: нестинг
Зачем нестинг влияет на цену
Цена резки — не «площадь детали × стоимость материала». Цена зависит от числа листов, которые пойдут в производство. Два варианта укладки одного и того же набора деталей могут дать 3 листа или 4 — и это разница в материале и времени.
Нестинг — задача из класса 2D bin packing; точное решение в общем случае слишком дорого по времени, поэтому на практике используют эвристики:
- Bottom-left fill — быстро, плохо для непрямоугольных деталей;
- No-fit polygon (NFP) — точнее, детали могут вращаться; дороже по вычислениям;
- Генетические алгоритмы / имитация отжига — используются в промышленных CAM (ProNest, SigmaNEST), дают лучший % утилизации, но требуют времени.
Для расчётного сервиса (не CAM) достаточная точность — другая, чем для производственного планирования. Важно зафиксировать: результат нестинга в сервисе — оценка для КП, а не программа для станка. Это снимает претензии «на производстве укладка отличается».
Что нужно параметризовать
- Зазор между деталями (kerf + технологический отступ) — зависит от материала и толщины;
- Отступ от края листа;
- Допустимые углы поворота: ограничение 0°/90°/180° позволяет опираться на bounding box вместо no-fit polygon — быстрее и проще в реализации; произвольный угол требует полноценного NFP и существенно усложняет алгоритм;
- Стандартные размеры листов или произвольный ввод;
- Смешивание заказов на одном листе и приоритет: можно ли укладывать детали из разных КП вместе, что важнее при конфликте — это бизнес-правило, но без него модель данных и планировщик не сойдутся с процессом цеха.
Если эти параметры «зашиты» в код без возможности менять через конфиг или интерфейс — первая же смена поставщика металла превращается в задачу разработки.
Когда запускать нестинг
Нестинг — вычислительно дорогая операция, особенно при NFP или большом числе деталей. Три варианта: синхронно при загрузке файла (пользователь ждёт — простой UX, но риск таймаутов на сложных сборках), асинхронно в фоне с уведомлением (сложнее, но масштабируется), только при подтверждении заказа (экономит ресурсы, но убирает цену «на лету»). Выбор влияет на архитектуру очереди задач с первого дня.
Слой 3: генерация документа
Почему это не «просто вывод суммы»
Документ — единственное, что переходит из сервиса в реальный рабочий процесс офиса. Если КП — это скриншот или HTML-страница без фирменного шаблона и реквизитов, менеджер переносит данные в Word/Excel вручную. Автоматизация не окупается.
Минимально рабочий документ:
- Таблица позиций (деталь → количество → материал → толщина → цена);
- Итог с НДС / без — явно;
- Фирменный шаблон с реквизитами (логотип, ИНН, реквизиты);
- Уникальный номер КП и дата;
- Срок действия предложения;
- Условия оплаты и срок изготовления — то, что менеджер допишет вручную поверх PDF, если не заложить в шаблон сразу.
Форматы
PDF — стандарт для отправки клиенту. DOCX или XLSX — если офис хочет править документ перед отправкой (типично: ручная скидка, спецусловия под клиента, корректировка сроков).
Для PDF из разметки два частых пути. WeasyPrint строит PDF из HTML+CSS; JavaScript не исполняется, возможности CSS ограничены — разумный выбор для простых шаблонов без «живой» вёрстки. Headless Chrome / Playwright открывают страницу как в браузере: удобно, когда шаблон ведётся как полноценная веб-страница и нужна предсказуемая отрисовка сложной вёрстки; цена — зависимость от браузерного рантайма в продакшене и более тяжёлый деплой.
DOCX собирают через python-docx (ограничение: сложные слияния ячеек и плавающие изображения — неудобно). XLSX — через openpyxl (хорошо для табличных данных, плохо для фирменного шаблона с логотипом).
Где цепочка рвётся чаще всего
На стыках слоёв, а не внутри них:
- Парсер → нестинг: контур распознан, но координаты в нестинг передаются без нормализации единиц — детали «маленькие» или «гигантские».
- Нестинг → цена: число листов посчитано, но формула цены не учитывает неполный лист (платят за целый) — сумма в КП ниже реальной.
- Цена → документ: скидки, коэффициенты срочности, ручные правки менеджера — если их нет в модели данных, появятся как «допишем потом» и остановят пайплайн перед первым реальным заказом.
Все три точки разрыва — следствие отсутствия явного контракта данных между слоями. Чтобы не искать это в продакшене, имеет смысл зафиксировать набор решений ещё на этапе оценки.
Что зафиксировать до разработки
Короткий чеклист для тимлида или CTO перед тем, как принимать оценку в спринты:
- Определены классы входных файлов и поведение при неподдерживаемом формате.
- Зафиксировано: КП — оценка или гарантия? (влияет на юридический текст в документе и на алгоритм нестинга).
- Параметры нестинга (зазор, лист, поворот) выведены в конфиг, не в код.
- Шаблон документа согласован с отделом продаж до верстки генератора.
- Определён источник правды для цены материала: файл, каталог в БД или ручной ввод.
- Зафиксировано, кто и как обновляет цены и справочник материалов при смене прайса.
- Решено, показывать ли клиенту визуализацию укладки на листе или только итоговую цену (влияет на доверие к расчёту и на объём обращений в поддержку).
Продуктовый угол — что брать в MVP, а что откладывать — в статье про MVP. Постановка задачи и сроки первого контура по кейсу PrixCut; карточка продукта — в каталоге.
Если разбираете похожую задачу — brief@forzdev.ru или Telegram.