@@ -57,6 +57,8 @@ function hexToAci(hex: number): number {
5757
5858// ─── DXF string builder ───────────────────────────────────────────────────────
5959
60+ const HANDSEED_PLACEHOLDER = "\x00HANDSEED\x00" ;
61+
6062class DxfWriter {
6163 private readonly _lines : string [ ] = [ ] ;
6264 private _handleCounter = 1 ;
@@ -75,8 +77,49 @@ class DxfWriter {
7577 return ( this . _handleCounter ++ ) . toString ( 16 ) . toUpperCase ( ) ;
7678 }
7779
80+ /** Emits `0/kind 5/handle 100/AcDbEntity 8/layer 62/color` — the common entity prelude. */
81+ entityHeader ( kind : string , layer : string , color : number ) : this {
82+ this . _lines . push (
83+ "0" , kind ,
84+ "5" , this . handle ( ) ,
85+ "100" , "AcDbEntity" ,
86+ "8" , layer ,
87+ "62" , String ( color ) ,
88+ ) ;
89+ return this ;
90+ }
91+
92+ /** Emits `0/TABLE 2/name 5/handle 100/AcDbSymbolTable 70/count`. */
93+ symbolTable ( name : string , count : number ) : this {
94+ this . _lines . push (
95+ "0" , "TABLE" ,
96+ "2" , name ,
97+ "5" , this . handle ( ) ,
98+ "100" , "AcDbSymbolTable" ,
99+ "70" , String ( count ) ,
100+ ) ;
101+ return this ;
102+ }
103+
104+ /** Emits `0/kind 5/handle 100/AcDbSymbolTableRecord 100/recordSubclass`. */
105+ symbolRecord ( kind : string , recordSubclass : string ) : this {
106+ this . _lines . push (
107+ "0" , kind ,
108+ "5" , this . handle ( ) ,
109+ "100" , "AcDbSymbolTableRecord" ,
110+ "100" , recordSubclass ,
111+ ) ;
112+ return this ;
113+ }
114+
115+ writeHandSeed ( ) : this {
116+ this . _lines . push ( "9" , "$HANDSEED" , "5" , HANDSEED_PLACEHOLDER ) ;
117+ return this ;
118+ }
119+
78120 build ( ) : string {
79- return this . _lines . join ( "\n" ) + "\n" ;
121+ const out = this . _lines . join ( "\n" ) + "\n" ;
122+ return out . replace ( HANDSEED_PLACEHOLDER , this . _handleCounter . toString ( 16 ) . toUpperCase ( ) ) ;
80123 }
81124}
82125
@@ -273,45 +316,49 @@ export class DxfExporter {
273316 private _writeHeader ( w : DxfWriter , paperSpace : boolean ) : void {
274317 w . p ( 0 , "SECTION" ) . p ( 2 , "HEADER" ) ;
275318 w . p ( 9 , "$ACADVER" ) . p ( 1 , this . config . trueColor ? "AC1018" : "AC1015" ) ;
319+ w . p ( 9 , "$DWGCODEPAGE" ) . p ( 3 , "ANSI_1252" ) ;
276320 // 4 = millimetres (paper-space output), 6 = metres (world-unit output).
277321 w . p ( 9 , "$INSUNITS" ) . p ( 70 , paperSpace ? 4 : 6 ) ;
322+ w . writeHandSeed ( ) ;
278323 w . p ( 0 , "ENDSEC" ) ;
279324 }
280325
281326 private _writeTables ( w : DxfWriter , layers : DrawingLayer [ ] , blockNames : string [ ] ) : void {
282327 w . p ( 0 , "SECTION" ) . p ( 2 , "TABLES" ) ;
283328
284- w . p ( 0 , "TABLE" ) . p ( 2 , " VPORT") . p ( 70 , 0 ) . p ( 0 , "ENDTAB" ) ;
329+ w . symbolTable ( " VPORT", 0 ) . p ( 0 , "ENDTAB" ) ;
285330
286- w . p ( 0 , "TABLE" ) . p ( 2 , " LTYPE") . p ( 70 , 1 ) ;
287- w . p ( 0 , "LTYPE" ) . p ( 2 , "CONTINUOUS" ) . p ( 70 , 0 )
288- . p ( 3 , "Solid line" ) . p ( 72 , 65 ) . p ( 73 , 0 ) . n ( 40 , 0.0 ) ;
331+ w . symbolTable ( " LTYPE", 1 ) ;
332+ w . symbolRecord ( "LTYPE" , "AcDbLinetypeTableRecord" )
333+ . p ( 2 , "CONTINUOUS" ) . p ( 70 , 0 ) . p ( 3 , "Solid line" ) . p ( 72 , 65 ) . p ( 73 , 0 ) . n ( 40 , 0.0 ) ;
289334 w . p ( 0 , "ENDTAB" ) ;
290335
291- w . p ( 0 , "TABLE" ) . p ( 2 , " LAYER") . p ( 70 , layers . length ) ;
336+ w . symbolTable ( " LAYER", layers . length ) ;
292337 for ( const layer of layers ) {
293338 const aci = hexToAci ( layer . material . color . getHex ( ) ) ;
294- w . p ( 0 , "LAYER" ) . p ( 2 , layer . name ) . p ( 70 , 0 ) . p ( 62 , aci ) . p ( 6 , "CONTINUOUS" ) ;
339+ w . symbolRecord ( "LAYER" , "AcDbLayerTableRecord" )
340+ . p ( 2 , layer . name ) . p ( 70 , 0 ) . p ( 62 , aci ) . p ( 6 , "CONTINUOUS" ) ;
295341 }
296342 w . p ( 0 , "ENDTAB" ) ;
297343
298- w . p ( 0 , "TABLE" ) . p ( 2 , "STYLE" ) . p ( 70 , 1 ) ;
299- w . p ( 0 , "STYLE" ) . p ( 2 , "STANDARD" ) . p ( 70 , 0 )
344+ w . symbolTable ( "STYLE" , 1 ) ;
345+ w . symbolRecord ( "STYLE" , "AcDbTextStyleTableRecord" )
346+ . p ( 2 , "STANDARD" ) . p ( 70 , 0 )
300347 . n ( 40 , 0.0 ) . n ( 41 , 1.0 ) . n ( 50 , 0.0 ) . p ( 71 , 0 ) . n ( 42 , 0.2 )
301348 . p ( 3 , "txt" ) . p ( 4 , "" ) ;
302349 w . p ( 0 , "ENDTAB" ) ;
303350
304- w . p ( 0 , "TABLE" ) . p ( 2 , " VIEW") . p ( 70 , 0 ) . p ( 0 , "ENDTAB" ) ;
305- w . p ( 0 , "TABLE" ) . p ( 2 , " UCS") . p ( 70 , 0 ) . p ( 0 , "ENDTAB" ) ;
306- w . p ( 0 , "TABLE" ) . p ( 2 , " APPID") . p ( 70 , 1 ) ;
307- w . p ( 0 , "APPID " ) . p ( 2 , "ACAD" ) . p ( 70 , 0 ) ;
351+ w . symbolTable ( " VIEW", 0 ) . p ( 0 , "ENDTAB" ) ;
352+ w . symbolTable ( " UCS", 0 ) . p ( 0 , "ENDTAB" ) ;
353+ w . symbolTable ( " APPID", 1 ) ;
354+ w . symbolRecord ( "APPID" , "AcDbRegAppTableRecord " ) . p ( 2 , "ACAD" ) . p ( 70 , 0 ) ;
308355 w . p ( 0 , "ENDTAB" ) ;
309- w . p ( 0 , "TABLE" ) . p ( 2 , " DIMSTYLE") . p ( 70 , 0 ) . p ( 0 , "ENDTAB" ) ;
356+ w . symbolTable ( " DIMSTYLE", 0 ) . p ( 0 , "ENDTAB" ) ;
310357
311358 const records = [ "*Model_Space" , "*Paper_Space" , ...blockNames ] ;
312- w . p ( 0 , "TABLE" ) . p ( 2 , " BLOCK_RECORD") . p ( 70 , records . length ) ;
359+ w . symbolTable ( " BLOCK_RECORD", records . length ) ;
313360 for ( const name of records ) {
314- w . p ( 0 , "BLOCK_RECORD" ) . p ( 5 , w . handle ( ) ) . p ( 2 , name ) ;
361+ w . symbolRecord ( "BLOCK_RECORD" , "AcDbBlockTableRecord" ) . p ( 2 , name ) ;
315362 }
316363 w . p ( 0 , "ENDTAB" ) ;
317364
@@ -331,7 +378,7 @@ export class DxfExporter {
331378 }
332379
333380 private _writeBlock ( w : DxfWriter , name : string , geo : THREE . BufferGeometry | null ) : void {
334- w . p ( 0 , "BLOCK" ) . p ( 5 , w . handle ( ) ) . p ( 8 , "0" )
381+ w . p ( 0 , "BLOCK" ) . p ( 5 , w . handle ( ) ) . p ( 100 , "AcDbEntity" ) . p ( 8 , "0" ) . p ( 100 , "AcDbBlockBegin ")
335382 . p ( 2 , name ) . p ( 70 , 0 ) . n ( 10 , 0.0 ) . n ( 20 , 0.0 ) . n ( 30 , 0.0 )
336383 . p ( 3 , name ) . p ( 1 , "" ) ;
337384 if ( geo ) {
@@ -344,7 +391,7 @@ export class DxfExporter {
344391 this . _viewport = savedVp ;
345392 this . _paperSlot = savedPs ;
346393 }
347- w . p ( 0 , "ENDBLK" ) . p ( 5 , w . handle ( ) ) . p ( 8 , "0" ) ;
394+ w . p ( 0 , "ENDBLK" ) . p ( 5 , w . handle ( ) ) . p ( 100 , "AcDbEntity" ) . p ( 8 , "0" ) . p ( 100 , "AcDbBlockEnd ") ;
348395 }
349396
350397 /** Writes a rectangular border for the active viewport (no-op when no viewport is set). */
@@ -385,7 +432,7 @@ export class DxfExporter {
385432
386433 private _writeObjects ( w : DxfWriter ) : void {
387434 w . p ( 0 , "SECTION" ) . p ( 2 , "OBJECTS" ) ;
388- w . p ( 0 , "DICTIONARY" ) . p ( 5 , w . handle ( ) ) . p ( 330 , 0 ) . p ( 100 , "AcDbRootDictionary" ) ;
435+ w . p ( 0 , "DICTIONARY" ) . p ( 5 , w . handle ( ) ) . p ( 330 , 0 ) . p ( 100 , "AcDbDictionary" ) . p ( 281 , 1 ) ;
389436 w . p ( 0 , "ENDSEC" ) ;
390437 }
391438
@@ -570,12 +617,9 @@ export class DxfExporter {
570617 // In paper-space the INSERT position is transformed to mm; the scale must
571618 // also be multiplied so block geometry renders at the correct paper size.
572619 const scale = ins . scale * this . _scale ( ) ;
573- w . p ( 0 , "INSERT" )
574- . p ( 5 , w . handle ( ) )
575- . p ( 8 , "0" )
576- . p ( 62 , color ) ;
620+ w . entityHeader ( "INSERT" , "0" , color ) ;
577621 this . _emitTrueColor ( w , insStyle . color ) ;
578- w
622+ w . p ( 100 , "AcDbBlockReference" )
579623 . p ( 2 , ins . blockName )
580624 . n ( 10 , this . _tx ( ins . position . x ) )
581625 . n ( 20 , this . _ty ( ins . position . z ) )
@@ -618,15 +662,16 @@ export class DxfExporter {
618662 }
619663
620664 private _writeLine ( w : DxfWriter , x1 : number , y1 : number , x2 : number , y2 : number , layer : string , color : number , hex ?: number ) : void {
621- w . p ( 0 , "LINE" ) . p ( 5 , w . handle ( ) ) . p ( 8 , layer ) . p ( 62 , color ) ;
665+ w . entityHeader ( "LINE" , layer , color ) ;
622666 this . _emitTrueColor ( w , hex ) ;
623- w . n ( 10 , this . _tx ( x1 ) ) . n ( 20 , this . _ty ( y1 ) ) . n ( 30 , 0.0 )
667+ w . p ( 100 , "AcDbLine" )
668+ . n ( 10 , this . _tx ( x1 ) ) . n ( 20 , this . _ty ( y1 ) ) . n ( 30 , 0.0 )
624669 . n ( 11 , this . _tx ( x2 ) ) . n ( 21 , this . _ty ( y2 ) ) . n ( 31 , 0.0 ) ;
625670 }
626671
627672 /** Writes a LINE entity with coordinates already in DXF space (no transform applied). */
628673 private _writeRawLine ( w : DxfWriter , x1 : number , y1 : number , x2 : number , y2 : number , layer : string , color : number ) : void {
629- w . p ( 0 , "LINE" ) . p ( 5 , w . handle ( ) ) . p ( 8 , layer ) . p ( 62 , color )
674+ w . entityHeader ( "LINE" , layer , color ) . p ( 100 , "AcDbLine" )
630675 . n ( 10 , x1 ) . n ( 20 , y1 ) . n ( 30 , 0.0 )
631676 . n ( 11 , x2 ) . n ( 21 , y2 ) . n ( 31 , 0.0 ) ;
632677 }
@@ -643,9 +688,10 @@ export class DxfExporter {
643688 x3 : number , z3 : number ,
644689 layer : string , color : number , hex ?: number ,
645690 ) : void {
646- w . p ( 0 , "SOLID" ) . p ( 5 , w . handle ( ) ) . p ( 8 , layer ) . p ( 62 , color ) ;
691+ w . entityHeader ( "SOLID" , layer , color ) ;
647692 this . _emitTrueColor ( w , hex ) ;
648- w . n ( 10 , this . _tx ( x1 ) ) . n ( 20 , this . _ty ( z1 ) ) . n ( 30 , 0.0 )
693+ w . p ( 100 , "AcDbTrace" )
694+ . n ( 10 , this . _tx ( x1 ) ) . n ( 20 , this . _ty ( z1 ) ) . n ( 30 , 0.0 )
649695 . n ( 11 , this . _tx ( x2 ) ) . n ( 21 , this . _ty ( z2 ) ) . n ( 31 , 0.0 )
650696 . n ( 12 , this . _tx ( x3 ) ) . n ( 22 , this . _ty ( z3 ) ) . n ( 32 , 0.0 )
651697 . n ( 13 , this . _tx ( x3 ) ) . n ( 23 , this . _ty ( z3 ) ) . n ( 33 , 0.0 ) ;
@@ -673,9 +719,10 @@ export class DxfExporter {
673719 const dx = this . _tx ( x ) ;
674720 const dy = this . _ty ( y ) ;
675721 // Scale text height to match the coordinate units (mm in paper-space, world units otherwise).
676- w . p ( 0 , "TEXT" ) . p ( 5 , w . handle ( ) ) . p ( 8 , layer ) . p ( 62 , color ) ;
722+ w . entityHeader ( "TEXT" , layer , color ) ;
677723 this . _emitTrueColor ( w , hex ) ;
678- w . n ( 10 , dx ) . n ( 20 , dy ) . n ( 30 , 0.0 )
724+ w . p ( 100 , "AcDbText" )
725+ . n ( 10 , dx ) . n ( 20 , dy ) . n ( 30 , 0.0 )
679726 . n ( 40 , height * this . _scale ( ) )
680727 . n ( 50 , rotDeg )
681728 . p ( 1 , text ) ;
0 commit comments