Skip to content

Commit bd571df

Browse files
authored
fix: write valid DXF structure in DxfExporter (#722)
1 parent 0fbe6a5 commit bd571df

1 file changed

Lines changed: 78 additions & 31 deletions

File tree

packages/core/src/drawings/DxfManager/src/DxfExporter.ts

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ function hexToAci(hex: number): number {
5757

5858
// ─── DXF string builder ───────────────────────────────────────────────────────
5959

60+
const HANDSEED_PLACEHOLDER = "\x00HANDSEED\x00";
61+
6062
class 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

Comments
 (0)