Уж казалось бы, онлайн гляделок dxf — пруд пруди. Но кто сталкивался с удивительным форматом dxf, знают — сколько вьюеров, столько и вариантов отображения. К тому же, большинство таких гляделок используют бэкенд для рендера. Но зачем, неужели так сложно отобразить 2D‑чертёж в браузере? Насколько это может быть сложно?

Перебрав все open-source DXF-библиотеки для JavaScript, я в итоге сел писать свой парсер и рендерер. И здесь я расскажу о дико бесящем, но удивительном мире формата DXF.
«А где размеры?“
Первая попытка была наивной, я вообще не собирался погружаться в эту пучину. Берём первый попавшийся dxf-parser плюс three-dxf, соединяем, показываем. Базовые фигуры — линии, окружности, дуги — отрисовались нормально. Для моего проекта было приемлимо, я и не подозревал о проблемах. Потом я открыл настоящий инженерный чертёж…
Половины размеров не было. Пунктирные осевые линии рисовались сплошными. Зоны штриховки превратились в пустые контуры. А план этажа, нарисованный в повёрнутой системе координат, выглядел как абстрактная живопись. Я помчался смотреть, как рендерят этот файл другие вьюеры. Тут мои брови и полезли наверх — все просмотрщики показали разные картинки, каждый лажал в отображении разных сущностей. И не было ни одного, где можно было уверенно сказать: а вот здесь всё чётко!
Я временно переключился на dxf-viewer — наиболее развитую альтернативу с 37К скачиваний в месяц. Стало лучше, но — только линейные размеры (ни радиальных, ни угловых, ни ординатных), ни типов линий, ни стрелок LEADER. В каждом чертеже чего‑то не хватало.

Корень проблемы прост: DXF — формат возрастом 40 лет, с огромным количеством фич. Формально он открытый (Autodesk публикует DXF Reference), но документация отстаёт от реальных CAD‑реализаций, многие нюансы описаны скудно, а самое забавное: реальные файлы регулярно нарушают спецификацию: NanoCAD, Компас, QCAD генерируют файлы, которые формально invalid, но все ожидают, что просмотрщик их откроет.
Зачем вообще рендерить CAD в браузере
Краткий ответ: потому что можем. В 2026 году CAD уходит в веб точно так же, как в своё время офисные документы ушли в Google Docs.
ГИС и кадастр: наложить план участка на Leaflet‑карту, наложить инженерные коммуникации на OpenStreetMap
BIM и стройка: показать архитектурные планы на сайте застройщика
CNC и производство: превью заготовок в онлайн‑сервисах лазерной резки
Строительная техника: онлайн‑каталоги запчастей со схемами
Внутренние системы: предварительный просмотр файлов в инженерных CRM
Платные альтернативы типа Autodesk Viewer стоят денег, привязывают к облаку Autodesk и не дают гибкости в визуализации. Open‑source решения — либо устаревшие, либо неполные. Рынок есть — инструмента не хватает.
Кроличья нора DXF
Если вы никогда не работали с DXF — самое интересное впереди. Формат представляет собой плоский поток пронумерованных пар «код / значение». Никакой вложенности, никакого XML, никакого JSON. Просто тысячи строк вида:
0 LINE 8 Layer1 10 0.0 20 0.0 11 100.0 21 50.0
Код 0 — тип сущности. Код 8 — имя слоя. Коды 10/20 — координаты начальной точки X/Y. Коды 11/21 — конечной. Для LINE достаточно просто. Но потом вы открываете DIMENSION с 30+ кодами, HATCH с рекурсивными путями границ, MTEXT со своим встроенным языком форматирования (\P для переноса, \S для дробей, {\fArial;стилизованный текст}) для шрифтов… Ладно, справимся.
А потом появляется OCS — Object Coordinate System. Некоторые CAD‑инструменты сохраняют сущности в локальных системах координат, заданных вектором направления экструзии. Чтобы преобразовать их обратно в мировые координаты, нужно реализовать так называемый Arbitrary Axis Algorithm из спецификации DXF. Пропустите это — и чертежи, изначально нарисованные в 3D‑контексте, будут повёрнуты, отражены или сдвинуты.
Что в итоге получилось
После нескольких месяцев работы dxf‑render обрабатывает 22 типа сущностей — включая те, что другие библиотеки пропускают:
Все 7 типов размеров (linear, rotated, aligned, ordinate, radial, diametric, angular) — не только линейные
Типы линий разрешаются из таблицы LTYPE и применяются как геометрические паттерны штрихов
25 встроенных паттернов штриховки (ANSI31, HONEY, BRICK…) с корректным отсечением по границам
LEADER и MULTILEADER — те самые стрелки‑выноски, которые есть в каждом машиностроительном чертеже
Полный OCS через Arbitrary Axis Algorithm
Векторный текст через opentype.js — без растровых текстур, резкий на любом зуме
Приятный довесок: тёмная тема с мгновенным переключением
И даже с таким багажом покрыто не всё — редкие варианты размеров упрощены, некоторые стили штриховок пока в бэклоге. Но на типичных чертежах dxf‑render уже рисует больше и точнее большинства open‑source альтернатив для JS.
Пять строк, чтобы отрисовать DXF
import { parseDxf, createThreeObjectsFromDXF, loadDefaultFont } from "dxf-render"; const dxf = parseDxf(dxfText); await loadDefaultFont(); const { group } = await createThreeObjectsFromDXF(dxf); scene.add(group); // добавляем в любую сцену Three.js
Это весь основной API. Парсим, строим Three.js-объекты, добавляем в сцену. Работает с React, Vue, Svelte, Angular, vanilla JS — с чем угодно, что может хостить Three.js canvas.
Рабочие примеры на StackBlitz: React | Vue.
Парсер без Three.js
Есть отдельная точка входа, если нужны только данные:
import { parseDxf } from "dxf-render/parser"; const dxf = parseDxf(dxfText); // → layers, entities, blocks, styles, header — всё типизировано
Ноль зависимостей. Работает в Node.js, Deno, Bun. Полезно для извлечения данных, серверной обработки или когда хочется написать свой рендерер поверх.
Оптимизации через проблемы
Проблема draw calls
Первая наивная реализация создавала один THREE.Line на каждую DXF-сущность. Скромный план этажа на 5 000 линий = 5 000 draw calls = слайд-шоу.
Решение: GeometryCollector, который аккумулирует все отрезки, точки и треугольники по ключу layer::color, а затем сбрасывает их в объединённые BufferGeometry-объекты. 5 000 draw calls превратились в ~50. GPU не волнуют отдельные линии — ему важны батчи.
Эта оптимизация дала −78% draw calls на типовых чертежах. Для больших архитектурных файлов разница измеряется в десятках кадров в секунду.
Как не повесить UI
Большие DXF-файлы могут содержать 50 000+ сущностей. Синхронная обработка замораживает браузер. createThreeObjectsFromDXF() отдаёт управление event loop каждые ~16 мс и поддерживает AbortSignal для отмены:
const { group } = await createThreeObjectsFromDXF(dxf, { signal: abortController.signal, onProgress: (p) => updateProgressBar(p), });
Дополнительно сам парсинг вынесен в inline Web Worker — тяжёлая токенизация DXF не блокирует main thread. При отсутствии Worker API автоматический фолбэк на синхронный парсинг.
Мгновенная тёмная тема без перестроения сцены
В AutoCAD цветовой индекс 7 означает «чёрный на светлом фоне, белый на тёмном». Вместо того чтобы перестраивать всю сцену при переключении темы, я использую sentinel-значения цветов, отслеживаемые в MaterialCacheStore. Вызов materials.switchTheme(true) обновляет затронутые материалы in-place — тема переключается мгновенно, без пересборки геометрии.
Архитектура: что внутри
Проект вырос из простенького вью-компонента в монорепозиторий из пяти пакетов:
dxf-render— framework-agnostic парсер + Three.js рендерер (ядро, ~63% кода)dxf-interaction— общие для UI-компонентов контроллеры интерактива: измерения (расстояние / площадь / угол), привязки, выбор сущностей, подсветка фигур и слоёв. ЧистыеcreateX(...)фабрики поверх dxf-render — без какой-либо реактивностиdxf-vuer— обёртка под Vue 3: компоненты и composablesdxf-react— обёртка под React 18+: компоненты и хукиdxf-lit— Web Component на Lit: один кастомный элемент, который работает в любом фреймворке и в vanilla JS
Код разнесён по четырём слоям, снизу вверх:
dxf-render— чистая математика и рендер: парсинг, геометрия (measureDistance,findSnapPoint,findEntriesInRect), Three.js-примитивы. Ни состояния, ни pointer-событий.dxf-interaction— фреймворк-агностичные, но stateful машины состояний: пайплайн указателя, измерения, привязки, выбор. Состояние отдаётся наружу через колбэки (pushState/onHover).обёртки (
dxf-vuer/dxf-react/dxf-lit) — тонкие байндинги: создают контроллер и зеркалят его колбэки вref(Vue) /useState(React) /ReactiveController(Lit).компонент (
.vue/.tsx/ Lit-элемент) — разметка, CSS, props/events/slots.
Благодаря такому разделению добавить новую обёртку (Svelte, Solid, Angular) — значит написать только байндинги и компоненты; машины состояний переписывать не нужно, как это и было сделано для React и Lit.
packages/ ├── dxf-render/ ← парсер + Three.js рендерер (ядро) │ └── src/ │ ├── parser/ ← scanner, sections, 26 обработчиков сущностей │ ├── render/ ← геометрия, материалы, коллекторы │ │ ├── collectors/ ← 16 модулей на типы сущностей │ │ ├── text/ ← векторный текст, MTEXT-парсер, глифы │ │ ├── dimensions.ts │ │ └── hatch.ts │ ├── scene/ ← Three.js helpers (камера, orbit controls) │ ├── utils/ ← color, linetype, OCS resolvers, измерения │ └── workers/ ← parser Web Worker ├── dxf-interaction/ ← контроллеры интерактива (createX-фабрики) │ └── src/ ← measurement, areaMeasurement, angleMeasurement, │ snap, picking, highlight, rectangleSelection ├── dxf-vuer/ └── src/ ← components/ + composables/ (Vue 3) ├── dxf-react/ └── src/ ← components/ + hooks/ (React 18+) └── dxf-lit/ └── src/ ← dxf-viewer.ts + controllers/ (Web Component)
Две точки входа у ядра:
dxf-render— полный API (парсер + рендерер)dxf-render/parser— только парсер, ноль зависимостей, работает в Node.js, Deno, Bun
С чем пришлось пободаться
Рендеринг текста. Один только парсинг MTEXT занимает ~400 строк. Встроенный язык форматирования недостаточно документирован и непоследовательно реализован в разных CAD-инструментах. В итоге пришлось собрать кастомный glyph cache с вручную сделанными векторными путями для специальных символов, которые opentype.js не обрабатывает (знаки диаметра, плюс-минус, градусы).
Поломанные файлы. DXF из реального мира — битый регулярно. Сущности без обязательных кодов, цветовые индексы вне диапазона, сплайны с нулевым числом контрольных точек. Парсер должен быть одновременно параноиком и всепрощающим: валидировать всё и не падать ни на чём.
Рендеринг в main thread. Парсинг я вынес в воркер — это дало сильный буст. А вот само создание объектов всё ещё живёт в main thread. Переработать архитектуру так, чтобы передать уже готовые BufferGeometry-массивы из Worker’а в main thread — большая задача, и она пока в бэклоге. Однако замечу, что даже сейчас рендеринг происходит в разы быстрее аналогичных продуктов.
Что дальше
WebGL instancing для блоков — повторяющиеся INSERT уже шарят одну геометрию, но каждый инстанс — это всё ещё отдельный draw call. Настоящий
InstancedMeshсхлопнет тысячи одинаковых элементов (крепёж, болты, типовые узлы) в один вызов.DWG-поддержка — тут решение только через libredwg WASM. План подключить libredwg WASM как альтернативный вход парсера, превратить DWG в тот же внутренний AST, что и DXF, и рендерить общим пайплайном.
Новые обёртки — Svelte, Solid, Angular. Благодаря слою
dxf-interactionэто теперь только байндинги и компоненты, без переписывания логики.
Но главный приоритет — полноценный и качественный рендер.
Попробовать
npm install dxf-render three
GitHub — звездочки и звездули приветствуются
Демо-сайт — оцените рендер вашего файла DXF, попробуйте встроенные инструменты
Замечания по проблемам рендеринга с благодарностью принимаю в ишьюсы на GitHub.
peacemakerv
Спасибо, большое дело затеяли !