<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Timur Arbaev</title>
    <description>The latest articles on Forem by Timur Arbaev (@arbaev).</description>
    <link>https://forem.com/arbaev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3820138%2F678cd0b0-a2a9-4ac0-96a1-be435e1f922a.jpeg</url>
      <title>Forem: Timur Arbaev</title>
      <link>https://forem.com/arbaev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/arbaev"/>
    <language>en</language>
    <item>
      <title>Rendering AutoCAD DXF files in the browser — how and why I built dxf-render</title>
      <dc:creator>Timur Arbaev</dc:creator>
      <pubDate>Thu, 12 Mar 2026 10:55:46 +0000</pubDate>
      <link>https://forem.com/arbaev/rendering-autocad-dxf-files-in-the-browser-how-and-why-i-built-dxf-render-1h6i</link>
      <guid>https://forem.com/arbaev/rendering-autocad-dxf-files-in-the-browser-how-and-why-i-built-dxf-render-1h6i</guid>
      <description>&lt;p&gt;A client sends you an AutoCAD drawing. "Just display it in the browser," they say. How hard can it be?&lt;/p&gt;

&lt;p&gt;I found out the hard way. After trying every open-source DXF library for JavaScript, I ended up building my own. Here's the story of &lt;strong&gt;&lt;a href="https://github.com/arbaev/dxf-kit" rel="noopener noreferrer"&gt;dxf-render&lt;/a&gt;&lt;/strong&gt; and what I learned about the surprisingly weird world of the DXF format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dxf-vuer.netlify.app" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt; — drag and drop any DXF file to see it rendered.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Where are the dimensions?"
&lt;/h2&gt;

&lt;p&gt;My first attempt was simple: grab &lt;code&gt;dxf-parser&lt;/code&gt; + &lt;code&gt;three-dxf&lt;/code&gt;, wire them together, ship it. The basic shapes looked fine — lines, circles, arcs. Then I opened a real engineering drawing.&lt;/p&gt;

&lt;p&gt;Half the dimensions were gone. The dashed center lines showed as solid. Hatched areas were empty outlines. And a floor plan drawn in a rotated coordinate system looked like abstract art.&lt;/p&gt;

&lt;p&gt;I switched to &lt;code&gt;dxf-viewer&lt;/code&gt; — the most capable option out there at 37K monthly downloads. Better, but still: only linear dimensions (no radial, angular, ordinate), no linetype patterns, no LEADER arrows. Every drawing had something missing.&lt;/p&gt;

&lt;p&gt;The core issue? DXF is a 40-year-old format with a &lt;em&gt;lot&lt;/em&gt; of features, and building a renderer that handles real-world files — not just textbook examples — takes more than a few entity handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DXF rabbit hole
&lt;/h2&gt;

&lt;p&gt;If you've never worked with DXF, here's the fun part: the file format is a flat stream of numbered code/value pairs. No nesting, no XML, no JSON. Just thousands of lines like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
LINE
8
Layer1
10
0.0
20
0.0
11
100.0
21
50.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code 0 = entity type. Code 8 = layer name. Codes 10/20 = start point X/Y. Codes 11/21 = end point. Simple enough for LINE. But then you hit DIMENSION entities with 30+ codes, HATCH with recursive boundary paths, MTEXT with its own inline formatting language (&lt;code&gt;\P&lt;/code&gt; for newline, &lt;code&gt;\S&lt;/code&gt; for stacking fractions, &lt;code&gt;{\fArial;styled text}&lt;/code&gt;)...&lt;/p&gt;

&lt;p&gt;And then there's OCS — the Object Coordinate System. Some CAD tools save entities in local coordinate systems defined by an extrusion direction vector. You need to implement the "Arbitrary Axis Algorithm" from the DXF spec to transform them back to world coordinates. Skip this and your 3D-originated drawings will be rotated, flipped, or shifted.&lt;/p&gt;

&lt;p&gt;I wrote 25 entity handlers. Each one wrapped in try-catch, because real-world DXF files regularly break the spec. An entity parser that crashes on malformed data is useless — you need to skip the broken entity, log a warning, and keep going.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I ended up with
&lt;/h2&gt;

&lt;p&gt;After months of work, dxf-render handles 21 entity types — including the ones that other libraries skip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All 7 dimension types&lt;/strong&gt; (linear, rotated, aligned, ordinate, radial, diametric, angular) — not just linear&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linetype patterns&lt;/strong&gt; resolved from the LTYPE table and applied as geometric dash patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;25 built-in hatch patterns&lt;/strong&gt; (ANSI31, HONEY, BRICK...) with proper clipping to boundary paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LEADER and MULTILEADER&lt;/strong&gt; — those annotation arrows that show up in every mechanical drawing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full OCS&lt;/strong&gt; via the Arbitrary Axis Algorithm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector text&lt;/strong&gt; rendered with opentype.js — no bitmap textures, sharp at any zoom&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most satisfying moment was opening a complex architectural plan and seeing it come out right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five lines to render a DXF
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parseDxf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createThreeObjectsFromDXF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loadDefaultFont&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dxf-render&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dxf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseDxf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dxfText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadDefaultFont&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createThreeObjectsFromDXF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dxf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// add to any Three.js scene&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the core API. Parse, create Three.js objects, add to scene. It works with React, Vue, Svelte, Angular, vanilla JS — anything that can host a Three.js canvas.&lt;/p&gt;

&lt;p&gt;Full working examples: &lt;a href="https://stackblitz.com/github/arbaev/dxf-kit/tree/main/examples/vanilla-ts?file=src/main.ts&amp;amp;title=dxf-render+Vanilla+TS" rel="noopener noreferrer"&gt;Vanilla TS&lt;/a&gt; | &lt;a href="https://stackblitz.com/github/arbaev/dxf-kit/tree/main/examples/react?file=src/DxfViewer.tsx&amp;amp;title=dxf-render+React" rel="noopener noreferrer"&gt;React&lt;/a&gt; | &lt;a href="https://stackblitz.com/github/arbaev/dxf-kit/tree/main/examples/vue?file=src/App.vue&amp;amp;title=dxf-vuer+Vue+3" rel="noopener noreferrer"&gt;Vue&lt;/a&gt; — all on StackBlitz, runnable in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parser without Three.js
&lt;/h3&gt;

&lt;p&gt;There's a separate entry point for when you just need the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parseDxf&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dxf-render/parser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dxf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseDxf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dxfText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → layers, entities, blocks, styles, header variables — all typed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero dependencies. Works in Node.js, Deno, Bun. Useful for data extraction, server-side processing, or building your own renderer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance tricks I learned the hard way
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The draw call problem
&lt;/h3&gt;

&lt;p&gt;My first naive renderer created one &lt;code&gt;THREE.Line&lt;/code&gt; per DXF entity. A modest floor plan with 5,000 lines = 5,000 draw calls = slideshow.&lt;/p&gt;

&lt;p&gt;The fix: a &lt;code&gt;GeometryCollector&lt;/code&gt; that accumulates all line segments, points, and mesh triangles by &lt;code&gt;layer::color&lt;/code&gt; key, then flushes them into merged &lt;code&gt;BufferGeometry&lt;/code&gt; objects. 5,000 draw calls became ~50. The GPU doesn't care about individual lines — it cares about batches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping the UI alive
&lt;/h3&gt;

&lt;p&gt;Large DXF files can have 50,000+ entities. Processing them synchronously freezes the browser. &lt;code&gt;createThreeObjectsFromDXF()&lt;/code&gt; yields back to the event loop every ~16ms and supports &lt;code&gt;AbortSignal&lt;/code&gt; for cancellation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createThreeObjectsFromDXF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dxf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onProgress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;updateProgressBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Instant dark mode without re-rendering
&lt;/h3&gt;

&lt;p&gt;AutoCAD's color index 7 means "black on light background, white on dark." Instead of re-building the entire scene when the user toggles dark mode, I use sentinel color values tracked in a &lt;code&gt;MaterialCacheStore&lt;/code&gt;. Calling &lt;code&gt;materials.switchTheme(true)&lt;/code&gt; updates the affected materials in-place — the theme switch is instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it compares
&lt;/h2&gt;

&lt;p&gt;I'll let the table speak:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;dxf-render&lt;/th&gt;
&lt;th&gt;dxf-viewer&lt;/th&gt;
&lt;th&gt;dxf-parser&lt;/th&gt;
&lt;th&gt;three-dxf&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rendering&lt;/td&gt;
&lt;td&gt;✅ Three.js&lt;/td&gt;
&lt;td&gt;✅ Three.js&lt;/td&gt;
&lt;td&gt;❌ parse only&lt;/td&gt;
&lt;td&gt;✅ Three.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entity types&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;~15&lt;/td&gt;
&lt;td&gt;~15 parsed&lt;/td&gt;
&lt;td&gt;~8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linetype patterns&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ all solid&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dimension types&lt;/td&gt;
&lt;td&gt;all 7&lt;/td&gt;
&lt;td&gt;linear only&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEADER / MULTILEADER&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hatch patterns&lt;/td&gt;
&lt;td&gt;25 built-in&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCS support&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;Z-flip only&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;native&lt;/td&gt;
&lt;td&gt;.d.ts&lt;/td&gt;
&lt;td&gt;native&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;853&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parser-only mode&lt;/td&gt;
&lt;td&gt;✅ zero deps&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last updated&lt;/td&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No shade to these projects — they solved real problems for a lot of people. dxf-render just goes deeper on entity coverage and rendering accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with tests earlier.&lt;/strong&gt; I wrote most of the 853 tests after the fact. Having them from the start would have caught color resolution edge cases (ByLayer inheriting from ByBlock inside nested INSERTs) much sooner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't underestimate text.&lt;/strong&gt; MTEXT parsing alone is ~400 lines. The inline formatting language is underdocumented and inconsistently implemented across CAD tools. I ended up building a custom glyph cache with hand-crafted vector paths for special characters that opentype.js doesn't handle (diameter symbols, plus-minus signs, degree marks).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DXF files in the wild are broken.&lt;/strong&gt; Entities with missing required codes, color indices out of range, splines with zero control points. The parser needs to be paranoid and forgiving at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polyline width&lt;/strong&gt; — LWPOLYLINE with variable start/end width (common in P&amp;amp;ID diagrams)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entity interaction&lt;/strong&gt; — click and hover events via raycaster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More hatch patterns&lt;/strong&gt; — importing from the QCAD pattern library&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;dxf-render three
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/arbaev/dxf-kit" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt; — star if it's useful ⭐&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dxf-vuer.netlify.app" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt; — upload any DXF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://stackblitz.com/github/arbaev/dxf-kit/tree/main/examples/vanilla-ts?file=src/main.ts&amp;amp;title=dxf-render+Vanilla+TS" rel="noopener noreferrer"&gt;StackBlitz&lt;/a&gt;&lt;/strong&gt; — try it without installing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Vue 3 users: &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/dxf-vuer" rel="noopener noreferrer"&gt;dxf-vuer&lt;/a&gt;&lt;/strong&gt; wraps dxf-render into a drop-in viewer component.&lt;/p&gt;




&lt;p&gt;Working with CAD files in the browser? I'd love to hear about your use case — drop a comment or &lt;a href="https://github.com/arbaev/dxf-kit/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>threejs</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
