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

Перебрав все open-source DXF-библиотеки для JavaScript, я в итоге сел писать свой парсер и рендерер. И здесь я расскажу о дико бесящем, но удивительном мире формата DXF.

«А где размеры?“

Первая попытка была наивной, я вообще не собирался погружаться в эту пучину. Берём первый попавшийся dxf-parser плюс three-dxf, соединяем, показываем. Базовые фигуры — линии, окружности, дуги — отрисовались нормально. Для моего проекта было приемлимо, я и не подозревал о проблемах. Потом я открыл настоящий инженерный чертёж…

Половины размеров не было. Пунктирные осевые линии рисовались сплошными. Зоны штриховки превратились в пустые контуры. А план этажа, нарисованный в повёрнутой системе координат, выглядел как абстрактная живопись. Я помчался смотреть, как рендерят этот файл другие вьюеры. Тут мои брови и полезли наверх — все просмотрщики показали разные картинки, каждый лажал в отображении разных сущностей. И не было ни одного, где можно было уверенно сказать: а вот здесь всё чётко!

Я временно переключился на dxf-viewer — наиболее развитую альтернативу с 37К скачиваний в месяц. Стало лучше, но — только линейные размеры (ни радиальных, ни угловых, ни ординатных), ни типов линий, ни стрелок LEADER. В каждом чертеже чего‑то не хватало.

Редко у кого можно увидеть поддержку типов линий. Ещё реже — поддержку толщины линий. dxf-kit поддерживает и то и другое.
Редко у кого можно увидеть поддержку типов линий. Ещё реже — поддержку толщины линий. dxf-kit поддерживает и то и другое.

Корень проблемы прост: 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: компоненты и composables

  • dxf-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.


Комментарии (1)


  1. peacemakerv
    06.06.2026 09:49

    Спасибо, большое дело затеяли !