ofs-dxf-ts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +411 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/core/dxf-builder.d.ts +348 -0
- package/dist/core/dxf-builder.js +549 -0
- package/dist/core/dxf-document.d.ts +220 -0
- package/dist/core/dxf-document.js +529 -0
- package/dist/core/dxf-entities.d.ts +327 -0
- package/dist/core/dxf-entities.js +22 -0
- package/dist/core/dxf-serializer.d.ts +48 -0
- package/dist/core/dxf-serializer.js +595 -0
- package/dist/core/hatch-patterns.d.ts +15 -0
- package/dist/core/hatch-patterns.js +214 -0
- package/dist/core/modelspace.d.ts +106 -0
- package/dist/core/modelspace.js +351 -0
- package/dist/geometry/math.utils.d.ts +8 -0
- package/dist/geometry/math.utils.js +43 -0
- package/dist/geometry/offset.utils.d.ts +32 -0
- package/dist/geometry/offset.utils.js +87 -0
- package/dist/geometry/polyline.utils.d.ts +10 -0
- package/dist/geometry/polyline.utils.js +63 -0
- package/dist/geometry/text.utils.d.ts +15 -0
- package/dist/geometry/text.utils.js +28 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +29 -0
- package/dist/parser/auto-cad-color-index.d.ts +8 -0
- package/dist/parser/auto-cad-color-index.js +265 -0
- package/dist/parser/dim-style-codes.d.ts +5 -0
- package/dist/parser/dim-style-codes.js +32 -0
- package/dist/parser/dxf-parser.d.ts +16 -0
- package/dist/parser/dxf-parser.js +824 -0
- package/dist/parser/entities/3dface.d.ts +5 -0
- package/dist/parser/entities/3dface.js +76 -0
- package/dist/parser/entities/arc.d.ts +5 -0
- package/dist/parser/entities/arc.js +34 -0
- package/dist/parser/entities/attdef.d.ts +5 -0
- package/dist/parser/entities/attdef.js +86 -0
- package/dist/parser/entities/attribute.d.ts +5 -0
- package/dist/parser/entities/attribute.js +106 -0
- package/dist/parser/entities/circle.d.ts +5 -0
- package/dist/parser/entities/circle.js +40 -0
- package/dist/parser/entities/dimension.d.ts +5 -0
- package/dist/parser/entities/dimension.js +65 -0
- package/dist/parser/entities/ellipse.d.ts +5 -0
- package/dist/parser/entities/ellipse.js +42 -0
- package/dist/parser/entities/hatch.d.ts +5 -0
- package/dist/parser/entities/hatch.js +321 -0
- package/dist/parser/entities/insert.d.ts +5 -0
- package/dist/parser/entities/insert.js +52 -0
- package/dist/parser/entities/line.d.ts +5 -0
- package/dist/parser/entities/line.js +30 -0
- package/dist/parser/entities/lwpolyline.d.ts +5 -0
- package/dist/parser/entities/lwpolyline.js +101 -0
- package/dist/parser/entities/mtext.d.ts +5 -0
- package/dist/parser/entities/mtext.js +54 -0
- package/dist/parser/entities/point.d.ts +5 -0
- package/dist/parser/entities/point.js +30 -0
- package/dist/parser/entities/polyline.d.ts +5 -0
- package/dist/parser/entities/polyline.js +85 -0
- package/dist/parser/entities/solid.d.ts +5 -0
- package/dist/parser/entities/solid.js +35 -0
- package/dist/parser/entities/spline.d.ts +5 -0
- package/dist/parser/entities/spline.js +69 -0
- package/dist/parser/entities/text.d.ts +5 -0
- package/dist/parser/entities/text.js +47 -0
- package/dist/parser/entities/vertex.d.ts +5 -0
- package/dist/parser/entities/vertex.js +59 -0
- package/dist/parser/extended-data-parser.d.ts +18 -0
- package/dist/parser/extended-data-parser.js +100 -0
- package/dist/parser/index.d.ts +10 -0
- package/dist/parser/index.js +9 -0
- package/dist/parser/log.d.ts +14 -0
- package/dist/parser/log.js +39 -0
- package/dist/parser/parse-helpers.d.ts +23 -0
- package/dist/parser/parse-helpers.js +128 -0
- package/dist/parser/scanner.d.ts +29 -0
- package/dist/parser/scanner.js +143 -0
- package/dist/parser/types.d.ts +78 -0
- package/dist/parser/types.js +5 -0
- package/dist/symbols/slope-symbol.d.ts +21 -0
- package/dist/symbols/slope-symbol.js +81 -0
- package/dist/symbols/structures.d.ts +9 -0
- package/dist/symbols/structures.js +120 -0
- package/dist/template/blank-document.d.ts +13 -0
- package/dist/template/blank-document.js +13 -0
- package/dist/template/block-registry.d.ts +16 -0
- package/dist/template/block-registry.js +135 -0
- package/dist/template/index.d.ts +17 -0
- package/dist/template/index.js +17 -0
- package/dist/template/template-injector.d.ts +45 -0
- package/dist/template/template-injector.js +487 -0
- package/dist/template/template-reader.d.ts +28 -0
- package/dist/template/template-reader.js +80 -0
- package/dist/types/index.d.ts +24 -0
- package/dist/types/index.js +8 -0
- package/package.json +59 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DXF Format Serializer.
|
|
3
|
+
* Converts internal entity representations and table records to DXF tag text.
|
|
4
|
+
* Section assembly (tables, blocks, objects, handle seeding) lives in
|
|
5
|
+
* template/template-injector, which serves both template and blank documents.
|
|
6
|
+
*/
|
|
7
|
+
import { HATCH_PATTERNS } from './hatch-patterns.js';
|
|
8
|
+
let handleCounter = 1000;
|
|
9
|
+
export function nextHandle() {
|
|
10
|
+
return (handleCounter++).toString(16).toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
export function resetHandleCounter(start = 1000) {
|
|
13
|
+
handleCounter = start;
|
|
14
|
+
}
|
|
15
|
+
/** The handle `nextHandle()` will return next — the value to store in $HANDSEED. */
|
|
16
|
+
export function peekHandle() {
|
|
17
|
+
return handleCounter.toString(16).toUpperCase();
|
|
18
|
+
}
|
|
19
|
+
/** Format a computed number for DXF: no exponent notation, no negative zero. */
|
|
20
|
+
export function fmt(n) {
|
|
21
|
+
if (!Number.isFinite(n) || Math.abs(n) < 1e-12)
|
|
22
|
+
return '0';
|
|
23
|
+
const s = String(n);
|
|
24
|
+
return s.includes('e') ? n.toFixed(12).replace(/\.?0+$/, '') : s;
|
|
25
|
+
}
|
|
26
|
+
// ─── Entity Serializer ───
|
|
27
|
+
function serializeEntity(entity, ctx = {}) {
|
|
28
|
+
switch (entity.type) {
|
|
29
|
+
case 'LINE': return serializeLine(entity, ctx);
|
|
30
|
+
case 'LWPOLYLINE': return serializeLwPolyline(entity, ctx);
|
|
31
|
+
case 'POLYLINE': return serializePolyline(entity, ctx);
|
|
32
|
+
case 'CIRCLE': return serializeCircle(entity, ctx);
|
|
33
|
+
case 'ARC': return serializeArc(entity, ctx);
|
|
34
|
+
case 'ELLIPSE': return serializeEllipse(entity, ctx);
|
|
35
|
+
case 'TEXT': return serializeText(entity, ctx);
|
|
36
|
+
case 'MTEXT': return serializeMText(entity, ctx);
|
|
37
|
+
case 'INSERT': return serializeInsert(entity, ctx);
|
|
38
|
+
case 'POINT': return serializePoint(entity, ctx);
|
|
39
|
+
case 'SOLID': return serializeSolid(entity, ctx);
|
|
40
|
+
case 'TRACE': return serializeTrace(entity, ctx);
|
|
41
|
+
case 'SPLINE': return serializeSpline(entity, ctx);
|
|
42
|
+
case 'HATCH': return serializeHatch(entity, ctx);
|
|
43
|
+
case 'DIMENSION': return serializeDimension(entity, ctx);
|
|
44
|
+
case 'LEADER': return serializeLeader(entity, ctx);
|
|
45
|
+
case 'VIEWPORT': return serializeViewport(entity, ctx);
|
|
46
|
+
case 'IMAGE': return serializeImage(entity, ctx);
|
|
47
|
+
case 'WIPEOUT': return serializeWipeout(entity, ctx);
|
|
48
|
+
case 'REGION': return serializeRegion(entity, ctx);
|
|
49
|
+
case 'MESH': return serializeMesh(entity, ctx);
|
|
50
|
+
case 'MLEADER': return serializeMLeader(entity, ctx);
|
|
51
|
+
default: return '';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function entityCommon(entity, ctx, handle = entity.handle ?? nextHandle()) {
|
|
55
|
+
const lines = ['5', handle];
|
|
56
|
+
if (ctx.owner)
|
|
57
|
+
lines.push('330', ctx.owner);
|
|
58
|
+
lines.push('100', 'AcDbEntity');
|
|
59
|
+
if (ctx.paperSpace)
|
|
60
|
+
lines.push('67', '1');
|
|
61
|
+
lines.push('8', entity.layer);
|
|
62
|
+
if (entity.linetype && entity.linetype.toUpperCase() !== 'BYLAYER') {
|
|
63
|
+
lines.push('6', entity.linetype);
|
|
64
|
+
}
|
|
65
|
+
if (entity.color !== undefined && entity.color !== 256) {
|
|
66
|
+
lines.push('62', String(entity.color));
|
|
67
|
+
}
|
|
68
|
+
if (entity.lineweight !== undefined) {
|
|
69
|
+
lines.push('370', String(entity.lineweight));
|
|
70
|
+
}
|
|
71
|
+
return lines;
|
|
72
|
+
}
|
|
73
|
+
/** Single-line DXF values cannot contain line breaks. */
|
|
74
|
+
function oneLine(text) {
|
|
75
|
+
return text.replace(/\r?\n/g, ' ');
|
|
76
|
+
}
|
|
77
|
+
function serializeLine(e, ctx) {
|
|
78
|
+
const lines = ['0', 'LINE', ...entityCommon(e, ctx)];
|
|
79
|
+
lines.push('100', 'AcDbLine');
|
|
80
|
+
lines.push('10', String(e.start[0]));
|
|
81
|
+
lines.push('20', String(e.start[1]));
|
|
82
|
+
lines.push('30', '0');
|
|
83
|
+
lines.push('11', String(e.end[0]));
|
|
84
|
+
lines.push('21', String(e.end[1]));
|
|
85
|
+
lines.push('31', '0');
|
|
86
|
+
return lines.join('\n');
|
|
87
|
+
}
|
|
88
|
+
function serializeLwPolyline(e, ctx) {
|
|
89
|
+
const lines = ['0', 'LWPOLYLINE', ...entityCommon(e, ctx)];
|
|
90
|
+
lines.push('100', 'AcDbPolyline');
|
|
91
|
+
lines.push('90', String(e.points.length));
|
|
92
|
+
lines.push('70', e.closed ? '1' : '0');
|
|
93
|
+
for (const pt of e.points) {
|
|
94
|
+
lines.push('10', String(pt[0]));
|
|
95
|
+
lines.push('20', String(pt[1]));
|
|
96
|
+
}
|
|
97
|
+
return lines.join('\n');
|
|
98
|
+
}
|
|
99
|
+
function serializeCircle(e, ctx) {
|
|
100
|
+
const lines = ['0', 'CIRCLE', ...entityCommon(e, ctx)];
|
|
101
|
+
lines.push('100', 'AcDbCircle');
|
|
102
|
+
lines.push('10', String(e.center[0]));
|
|
103
|
+
lines.push('20', String(e.center[1]));
|
|
104
|
+
lines.push('30', '0');
|
|
105
|
+
lines.push('40', String(e.radius));
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
function serializeArc(e, ctx) {
|
|
109
|
+
const lines = ['0', 'ARC', ...entityCommon(e, ctx)];
|
|
110
|
+
lines.push('100', 'AcDbCircle');
|
|
111
|
+
lines.push('10', String(e.center[0]));
|
|
112
|
+
lines.push('20', String(e.center[1]));
|
|
113
|
+
lines.push('30', '0');
|
|
114
|
+
lines.push('40', String(e.radius));
|
|
115
|
+
lines.push('100', 'AcDbArc');
|
|
116
|
+
lines.push('50', String(e.startAngle));
|
|
117
|
+
lines.push('51', String(e.endAngle));
|
|
118
|
+
return lines.join('\n');
|
|
119
|
+
}
|
|
120
|
+
function serializeText(e, ctx) {
|
|
121
|
+
const lines = ['0', 'TEXT', ...entityCommon(e, ctx)];
|
|
122
|
+
lines.push('100', 'AcDbText');
|
|
123
|
+
lines.push('10', String(e.position[0]));
|
|
124
|
+
lines.push('20', String(e.position[1]));
|
|
125
|
+
lines.push('30', '0');
|
|
126
|
+
lines.push('40', String(e.height));
|
|
127
|
+
lines.push('1', oneLine(e.text));
|
|
128
|
+
if (e.rotation)
|
|
129
|
+
lines.push('50', String(e.rotation));
|
|
130
|
+
if (e.style && e.style !== 'Standard')
|
|
131
|
+
lines.push('7', e.style);
|
|
132
|
+
const aligned = e.halign !== 0 || e.valign !== 0;
|
|
133
|
+
if (aligned) {
|
|
134
|
+
const ap = e.alignmentPoint ?? e.position;
|
|
135
|
+
lines.push('72', String(e.halign));
|
|
136
|
+
lines.push('11', String(ap[0]), '21', String(ap[1]), '31', '0');
|
|
137
|
+
}
|
|
138
|
+
lines.push('100', 'AcDbText');
|
|
139
|
+
if (aligned)
|
|
140
|
+
lines.push('73', String(e.valign));
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
function serializeMText(e, ctx) {
|
|
144
|
+
const lines = ['0', 'MTEXT', ...entityCommon(e, ctx)];
|
|
145
|
+
lines.push('100', 'AcDbMText');
|
|
146
|
+
lines.push('10', String(e.position[0]));
|
|
147
|
+
lines.push('20', String(e.position[1]));
|
|
148
|
+
lines.push('30', '0');
|
|
149
|
+
lines.push('40', String(e.height));
|
|
150
|
+
lines.push('41', String(e.width));
|
|
151
|
+
lines.push('71', String(e.attachmentPoint));
|
|
152
|
+
// MTEXT line breaks are "\P"; content longer than 250 chars goes in code-3 chunks.
|
|
153
|
+
const content = e.text.replace(/\r?\n/g, '\\P');
|
|
154
|
+
const chunks = content.match(/[\s\S]{1,250}/g) ?? [''];
|
|
155
|
+
for (let i = 0; i < chunks.length - 1; i++)
|
|
156
|
+
lines.push('3', chunks[i]);
|
|
157
|
+
lines.push('1', chunks[chunks.length - 1]);
|
|
158
|
+
if (e.style && e.style !== 'Standard')
|
|
159
|
+
lines.push('7', e.style);
|
|
160
|
+
if (e.rotation)
|
|
161
|
+
lines.push('50', String(e.rotation));
|
|
162
|
+
return lines.join('\n');
|
|
163
|
+
}
|
|
164
|
+
function serializeInsert(e, ctx) {
|
|
165
|
+
const lines = ['0', 'INSERT', ...entityCommon(e, ctx)];
|
|
166
|
+
lines.push('100', 'AcDbBlockReference');
|
|
167
|
+
lines.push('2', e.blockName);
|
|
168
|
+
lines.push('10', String(e.position[0]));
|
|
169
|
+
lines.push('20', String(e.position[1]));
|
|
170
|
+
lines.push('30', '0');
|
|
171
|
+
if (e.xScale !== 1)
|
|
172
|
+
lines.push('41', String(e.xScale));
|
|
173
|
+
if (e.yScale !== 1)
|
|
174
|
+
lines.push('42', String(e.yScale));
|
|
175
|
+
if (e.zScale !== 1)
|
|
176
|
+
lines.push('43', String(e.zScale));
|
|
177
|
+
if (e.rotation)
|
|
178
|
+
lines.push('50', String(e.rotation));
|
|
179
|
+
return lines.join('\n');
|
|
180
|
+
}
|
|
181
|
+
function serializePoint(e, ctx) {
|
|
182
|
+
const lines = ['0', 'POINT', ...entityCommon(e, ctx)];
|
|
183
|
+
lines.push('100', 'AcDbPoint');
|
|
184
|
+
lines.push('10', String(e.position[0]));
|
|
185
|
+
lines.push('20', String(e.position[1]));
|
|
186
|
+
lines.push('30', '0');
|
|
187
|
+
return lines.join('\n');
|
|
188
|
+
}
|
|
189
|
+
function serializePolyline(e, ctx) {
|
|
190
|
+
const handle = nextHandle();
|
|
191
|
+
const lines = ['0', 'POLYLINE', ...entityCommon(e, ctx, handle)];
|
|
192
|
+
lines.push('100', 'AcDb3dPolyline');
|
|
193
|
+
lines.push('66', '1');
|
|
194
|
+
lines.push('10', '0', '20', '0', '30', '0');
|
|
195
|
+
lines.push('70', String(8 | (e.closed ? 1 : 0))); // 8 = 3D polyline
|
|
196
|
+
const parts = [lines.join('\n')];
|
|
197
|
+
const sub = (type) => {
|
|
198
|
+
const l = ['0', type, '5', nextHandle(), '330', handle, '100', 'AcDbEntity'];
|
|
199
|
+
if (ctx.paperSpace)
|
|
200
|
+
l.push('67', '1');
|
|
201
|
+
l.push('8', e.layer);
|
|
202
|
+
return l;
|
|
203
|
+
};
|
|
204
|
+
for (const pt of e.points) {
|
|
205
|
+
const vl = sub('VERTEX');
|
|
206
|
+
vl.push('100', 'AcDbVertex', '100', 'AcDb3dPolylineVertex');
|
|
207
|
+
vl.push('10', String(pt[0]), '20', String(pt[1]), '30', String(pt[2] || 0), '70', '32');
|
|
208
|
+
parts.push(vl.join('\n'));
|
|
209
|
+
}
|
|
210
|
+
parts.push(sub('SEQEND').join('\n'));
|
|
211
|
+
return parts.join('\n');
|
|
212
|
+
}
|
|
213
|
+
function serializeEllipse(e, ctx) {
|
|
214
|
+
const lines = ['0', 'ELLIPSE', ...entityCommon(e, ctx)];
|
|
215
|
+
lines.push('100', 'AcDbEllipse');
|
|
216
|
+
lines.push('10', String(e.center[0]), '20', String(e.center[1]), '30', '0');
|
|
217
|
+
lines.push('11', String(e.majorAxis[0]), '21', String(e.majorAxis[1]), '31', '0');
|
|
218
|
+
lines.push('40', String(e.ratioMinorToMajor));
|
|
219
|
+
lines.push('41', String(e.startParam));
|
|
220
|
+
lines.push('42', String(e.endParam));
|
|
221
|
+
return lines.join('\n');
|
|
222
|
+
}
|
|
223
|
+
function serializeSolid(e, ctx) {
|
|
224
|
+
const lines = ['0', 'SOLID', ...entityCommon(e, ctx)];
|
|
225
|
+
lines.push('100', 'AcDbTrace');
|
|
226
|
+
lines.push('10', String(e.points[0][0]), '20', String(e.points[0][1]), '30', '0');
|
|
227
|
+
lines.push('11', String(e.points[1][0]), '21', String(e.points[1][1]), '31', '0');
|
|
228
|
+
lines.push('12', String(e.points[2][0]), '22', String(e.points[2][1]), '32', '0');
|
|
229
|
+
lines.push('13', String(e.points[3][0]), '23', String(e.points[3][1]), '33', '0');
|
|
230
|
+
return lines.join('\n');
|
|
231
|
+
}
|
|
232
|
+
function serializeTrace(e, ctx) {
|
|
233
|
+
const lines = ['0', 'TRACE', ...entityCommon(e, ctx)];
|
|
234
|
+
lines.push('100', 'AcDbTrace');
|
|
235
|
+
lines.push('10', String(e.points[0][0]), '20', String(e.points[0][1]), '30', '0');
|
|
236
|
+
lines.push('11', String(e.points[1][0]), '21', String(e.points[1][1]), '31', '0');
|
|
237
|
+
lines.push('12', String(e.points[2][0]), '22', String(e.points[2][1]), '32', '0');
|
|
238
|
+
lines.push('13', String(e.points[3][0]), '23', String(e.points[3][1]), '33', '0');
|
|
239
|
+
return lines.join('\n');
|
|
240
|
+
}
|
|
241
|
+
function serializeSpline(e, ctx) {
|
|
242
|
+
const lines = ['0', 'SPLINE', ...entityCommon(e, ctx)];
|
|
243
|
+
lines.push('100', 'AcDbSpline');
|
|
244
|
+
lines.push('70', e.closed ? '11' : '8');
|
|
245
|
+
lines.push('71', String(e.degree));
|
|
246
|
+
lines.push('72', String(e.knots.length));
|
|
247
|
+
lines.push('73', String(e.controlPoints.length));
|
|
248
|
+
lines.push('74', String(e.fitPoints.length));
|
|
249
|
+
for (const k of e.knots)
|
|
250
|
+
lines.push('40', String(k));
|
|
251
|
+
if (e.weights) {
|
|
252
|
+
for (const w of e.weights)
|
|
253
|
+
lines.push('41', String(w));
|
|
254
|
+
}
|
|
255
|
+
for (const cp of e.controlPoints)
|
|
256
|
+
lines.push('10', String(cp[0]), '20', String(cp[1]), '30', '0');
|
|
257
|
+
for (const fp of e.fitPoints)
|
|
258
|
+
lines.push('11', String(fp[0]), '21', String(fp[1]), '31', '0');
|
|
259
|
+
return lines.join('\n');
|
|
260
|
+
}
|
|
261
|
+
// ─── HATCH ───
|
|
262
|
+
function rotateScale(p, scale, angleDeg) {
|
|
263
|
+
const a = angleDeg * Math.PI / 180;
|
|
264
|
+
const x = p[0] * scale, y = p[1] * scale;
|
|
265
|
+
return [x * Math.cos(a) - y * Math.sin(a), x * Math.sin(a) + y * Math.cos(a)];
|
|
266
|
+
}
|
|
267
|
+
/** Resolve a hatch's pattern lines (custom or built-in), scaled and rotated for output. */
|
|
268
|
+
export function hatchPatternLines(e) {
|
|
269
|
+
const base = e.patternLines ?? HATCH_PATTERNS[e.patternName.toUpperCase()];
|
|
270
|
+
if (!base) {
|
|
271
|
+
throw new Error(`Unknown hatch pattern "${e.patternName}". Use one of ${Object.keys(HATCH_PATTERNS).join(', ')}, ` +
|
|
272
|
+
`or pass patternLines.`);
|
|
273
|
+
}
|
|
274
|
+
return base.map(([angle, basePoint, offset, dashes]) => [
|
|
275
|
+
angle + e.angle,
|
|
276
|
+
rotateScale(basePoint, e.scale, e.angle),
|
|
277
|
+
rotateScale(offset, e.scale, e.angle),
|
|
278
|
+
dashes.map(d => d * e.scale),
|
|
279
|
+
]);
|
|
280
|
+
}
|
|
281
|
+
function serializeHatch(e, ctx) {
|
|
282
|
+
const lines = ['0', 'HATCH', ...entityCommon(e, ctx)];
|
|
283
|
+
lines.push('100', 'AcDbHatch');
|
|
284
|
+
lines.push('10', '0', '20', '0', '30', '0');
|
|
285
|
+
lines.push('210', '0', '220', '0', '230', '1');
|
|
286
|
+
lines.push('2', e.solid ? 'SOLID' : e.patternName.toUpperCase());
|
|
287
|
+
lines.push('70', e.solid ? '1' : '0');
|
|
288
|
+
lines.push('71', '0'); // not associative
|
|
289
|
+
lines.push('91', String(e.boundaries.length));
|
|
290
|
+
e.boundaries.forEach((b, i) => {
|
|
291
|
+
lines.push('92', i === 0 ? '3' : '2'); // 2 = polyline path, 1 = external
|
|
292
|
+
lines.push('72', '0'); // no bulges
|
|
293
|
+
lines.push('73', '1'); // closed
|
|
294
|
+
lines.push('93', String(b.points.length));
|
|
295
|
+
for (const pt of b.points)
|
|
296
|
+
lines.push('10', String(pt[0]), '20', String(pt[1]));
|
|
297
|
+
lines.push('97', '0'); // no source boundary objects
|
|
298
|
+
});
|
|
299
|
+
lines.push('75', '0'); // odd parity (holes work)
|
|
300
|
+
lines.push('76', e.patternLines ? '2' : '1'); // 1 = predefined, 2 = custom
|
|
301
|
+
if (!e.solid) {
|
|
302
|
+
const patternLines = hatchPatternLines(e);
|
|
303
|
+
lines.push('52', fmt(e.angle));
|
|
304
|
+
lines.push('41', fmt(e.scale));
|
|
305
|
+
lines.push('77', '0');
|
|
306
|
+
lines.push('78', String(patternLines.length));
|
|
307
|
+
for (const [angle, base, offset, dashes] of patternLines) {
|
|
308
|
+
lines.push('53', fmt(angle));
|
|
309
|
+
lines.push('43', fmt(base[0]), '44', fmt(base[1]));
|
|
310
|
+
lines.push('45', fmt(offset[0]), '46', fmt(offset[1]));
|
|
311
|
+
lines.push('79', String(dashes.length));
|
|
312
|
+
for (const d of dashes)
|
|
313
|
+
lines.push('49', fmt(d));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
lines.push('98', '0');
|
|
317
|
+
return lines.join('\n');
|
|
318
|
+
}
|
|
319
|
+
function serializeDimension(e, ctx) {
|
|
320
|
+
const lines = ['0', 'DIMENSION', ...entityCommon(e, ctx)];
|
|
321
|
+
lines.push('100', 'AcDbDimension');
|
|
322
|
+
lines.push('2', '*D0'); // block name auto-generated
|
|
323
|
+
lines.push('10', String(e.definitionPoint[0]), '20', String(e.definitionPoint[1]), '30', '0');
|
|
324
|
+
lines.push('11', String(e.textMidpoint[0]), '21', String(e.textMidpoint[1]), '31', '0');
|
|
325
|
+
lines.push('70', String(e.dimensionType));
|
|
326
|
+
if (e.text)
|
|
327
|
+
lines.push('1', e.text);
|
|
328
|
+
if (e.style)
|
|
329
|
+
lines.push('3', e.style);
|
|
330
|
+
if (e.dimensionType <= 1) {
|
|
331
|
+
lines.push('100', 'AcDbAlignedDimension');
|
|
332
|
+
if (e.point1)
|
|
333
|
+
lines.push('13', String(e.point1[0]), '23', String(e.point1[1]), '33', '0');
|
|
334
|
+
if (e.point2)
|
|
335
|
+
lines.push('14', String(e.point2[0]), '24', String(e.point2[1]), '34', '0');
|
|
336
|
+
if (e.rotation)
|
|
337
|
+
lines.push('50', String(e.rotation));
|
|
338
|
+
if (e.dimensionType === 0)
|
|
339
|
+
lines.push('100', 'AcDbRotatedDimension');
|
|
340
|
+
}
|
|
341
|
+
return lines.join('\n');
|
|
342
|
+
}
|
|
343
|
+
function serializeLeader(e, ctx) {
|
|
344
|
+
const lines = ['0', 'LEADER', ...entityCommon(e, ctx)];
|
|
345
|
+
lines.push('100', 'AcDbLeader');
|
|
346
|
+
if (e.style)
|
|
347
|
+
lines.push('3', e.style);
|
|
348
|
+
lines.push('71', e.hasArrowhead ? '1' : '0');
|
|
349
|
+
lines.push('72', '0'); // leader path type (0=straight)
|
|
350
|
+
lines.push('73', String(e.annotationType));
|
|
351
|
+
lines.push('76', String(e.vertices.length));
|
|
352
|
+
for (const v of e.vertices)
|
|
353
|
+
lines.push('10', String(v[0]), '20', String(v[1]), '30', '0');
|
|
354
|
+
return lines.join('\n');
|
|
355
|
+
}
|
|
356
|
+
// ─── VIEWPORT ───
|
|
357
|
+
function serializeViewport(e, ctx) {
|
|
358
|
+
const lines = ['0', 'VIEWPORT', ...entityCommon(e, ctx)];
|
|
359
|
+
lines.push('100', 'AcDbViewport');
|
|
360
|
+
lines.push('10', fmt(e.center[0]), '20', fmt(e.center[1]), '30', '0');
|
|
361
|
+
lines.push('40', fmt(e.width));
|
|
362
|
+
lines.push('41', fmt(e.height));
|
|
363
|
+
lines.push('68', String(e.status));
|
|
364
|
+
lines.push('69', String(e.id ?? e.status));
|
|
365
|
+
lines.push('12', fmt(e.viewCenter[0]), '22', fmt(e.viewCenter[1]));
|
|
366
|
+
lines.push('13', '0', '23', '0'); // snap base
|
|
367
|
+
lines.push('14', '10', '24', '10'); // snap spacing
|
|
368
|
+
lines.push('15', '10', '25', '10'); // grid spacing
|
|
369
|
+
lines.push('16', '0', '26', '0', '36', '1'); // view direction
|
|
370
|
+
lines.push('17', '0', '27', '0', '37', '0'); // view target
|
|
371
|
+
lines.push('42', '50'); // lens length
|
|
372
|
+
lines.push('43', '0', '44', '0'); // front/back clip
|
|
373
|
+
lines.push('45', fmt(e.viewHeight));
|
|
374
|
+
lines.push('50', '0', '51', '0'); // snap angle, twist
|
|
375
|
+
lines.push('72', '1000'); // circle zoom
|
|
376
|
+
for (const name of e.frozenLayers ?? []) {
|
|
377
|
+
const h = ctx.layerHandles?.get(name.toUpperCase());
|
|
378
|
+
if (h)
|
|
379
|
+
lines.push('331', h);
|
|
380
|
+
}
|
|
381
|
+
// 32768 = always set by AutoCAD; 16384 = display locked
|
|
382
|
+
lines.push('90', String(32768 | (e.locked ? 16384 : 0)));
|
|
383
|
+
lines.push('1', '');
|
|
384
|
+
lines.push('281', '0'); // render mode
|
|
385
|
+
lines.push('71', '1'); // UCS per viewport
|
|
386
|
+
lines.push('74', '0'); // UCS icon
|
|
387
|
+
lines.push('110', '0', '120', '0', '130', '0');
|
|
388
|
+
lines.push('111', '1', '121', '0', '131', '0');
|
|
389
|
+
lines.push('112', '0', '122', '1', '132', '0');
|
|
390
|
+
lines.push('79', '0');
|
|
391
|
+
lines.push('146', '0');
|
|
392
|
+
lines.push('170', '0'); // shade plot mode
|
|
393
|
+
lines.push('61', '5'); // grid frequency
|
|
394
|
+
lines.push('292', '1', '282', '1'); // default lighting
|
|
395
|
+
lines.push('141', '0', '142', '0'); // brightness, contrast
|
|
396
|
+
lines.push('63', '250', '421', '3355443'); // ambient light color
|
|
397
|
+
return lines.join('\n');
|
|
398
|
+
}
|
|
399
|
+
// ─── IMAGE ───
|
|
400
|
+
/** Convert a WCS point to IMAGE clip-boundary pixel coordinates (origin top-left). */
|
|
401
|
+
function wcsToImagePixel(e, p) {
|
|
402
|
+
const [ux, uy] = e.uVector, [vx, vy] = e.vVector;
|
|
403
|
+
// origin of pixel (0, height) grid, see DXF IMAGE clip boundary convention
|
|
404
|
+
const ox = e.insertionPoint[0] + ux * 0.5 - vx * 0.5;
|
|
405
|
+
const oy = e.insertionPoint[1] + uy * 0.5 - vy * 0.5;
|
|
406
|
+
const dx = p[0] - ox, dy = p[1] - oy;
|
|
407
|
+
const det = ux * vy - uy * vx;
|
|
408
|
+
const px = (dx * vy - dy * vx) / det;
|
|
409
|
+
const qy = (ux * dy - uy * dx) / det;
|
|
410
|
+
return [px, e.imageSize[1] - qy];
|
|
411
|
+
}
|
|
412
|
+
function serializeImage(e, ctx) {
|
|
413
|
+
const refs = ctx.images?.get(e);
|
|
414
|
+
if (!refs) {
|
|
415
|
+
throw new Error('IMAGE entities must be serialized through DxfDocument (image definition missing).');
|
|
416
|
+
}
|
|
417
|
+
const clip = e.clipBoundary && e.clipBoundary.length >= 3 ? [...e.clipBoundary] : undefined;
|
|
418
|
+
if (clip) {
|
|
419
|
+
const [f, l] = [clip[0], clip[clip.length - 1]];
|
|
420
|
+
if (f[0] !== l[0] || f[1] !== l[1])
|
|
421
|
+
clip.push(f); // AutoCAD requires a closed clip polygon
|
|
422
|
+
}
|
|
423
|
+
const lines = ['0', 'IMAGE', ...entityCommon(e, ctx, refs.handle)];
|
|
424
|
+
lines.push('100', 'AcDbRasterImage');
|
|
425
|
+
lines.push('90', '0');
|
|
426
|
+
lines.push('10', fmt(e.insertionPoint[0]), '20', fmt(e.insertionPoint[1]), '30', '0');
|
|
427
|
+
lines.push('11', fmt(e.uVector[0]), '21', fmt(e.uVector[1]), '31', '0');
|
|
428
|
+
lines.push('12', fmt(e.vVector[0]), '22', fmt(e.vVector[1]), '32', '0');
|
|
429
|
+
lines.push('13', String(e.imageSize[0]), '23', String(e.imageSize[1]));
|
|
430
|
+
lines.push('340', refs.defHandle);
|
|
431
|
+
lines.push('70', String(1 | 2 | (clip ? 4 : 0))); // show, show unaligned, use clip
|
|
432
|
+
lines.push('280', clip ? '1' : '0');
|
|
433
|
+
lines.push('281', String(e.brightness ?? 50));
|
|
434
|
+
lines.push('282', String(e.contrast ?? 50));
|
|
435
|
+
lines.push('283', String(e.fade ?? 0));
|
|
436
|
+
lines.push('360', refs.reactorHandle);
|
|
437
|
+
if (clip) {
|
|
438
|
+
lines.push('71', '2', '91', String(clip.length));
|
|
439
|
+
for (const p of clip) {
|
|
440
|
+
const [px, py] = wcsToImagePixel(e, p);
|
|
441
|
+
lines.push('14', fmt(px), '24', fmt(py));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
lines.push('71', '1', '91', '2');
|
|
446
|
+
lines.push('14', '-0.5', '24', '-0.5');
|
|
447
|
+
lines.push('14', fmt(e.imageSize[0] - 0.5), '24', fmt(e.imageSize[1] - 0.5));
|
|
448
|
+
}
|
|
449
|
+
lines.push('290', '0');
|
|
450
|
+
return lines.join('\n');
|
|
451
|
+
}
|
|
452
|
+
function serializeWipeout(e, ctx) {
|
|
453
|
+
const lines = ['0', 'WIPEOUT', ...entityCommon(e, ctx)];
|
|
454
|
+
lines.push('100', 'AcDbWipeout');
|
|
455
|
+
lines.push('90', '0');
|
|
456
|
+
lines.push('10', String(e.insertionPoint[0]), '20', String(e.insertionPoint[1]), '30', '0');
|
|
457
|
+
lines.push('11', String(e.uVector[0]), '21', String(e.uVector[1]), '31', '0');
|
|
458
|
+
lines.push('12', String(e.vVector[0]), '22', String(e.vVector[1]), '32', '0');
|
|
459
|
+
lines.push('13', String(e.imageSize[0]), '23', String(e.imageSize[1]));
|
|
460
|
+
lines.push('70', '7');
|
|
461
|
+
lines.push('280', '1'); // clipping on
|
|
462
|
+
lines.push('71', '2'); // clip boundary type (2=polygon)
|
|
463
|
+
lines.push('91', String(e.clipBoundary.length));
|
|
464
|
+
for (const pt of e.clipBoundary)
|
|
465
|
+
lines.push('14', String(pt[0]), '24', String(pt[1]));
|
|
466
|
+
return lines.join('\n');
|
|
467
|
+
}
|
|
468
|
+
function serializeRegion(e, ctx) {
|
|
469
|
+
const lines = ['0', 'REGION', ...entityCommon(e, ctx)];
|
|
470
|
+
lines.push('100', 'AcDbModelerGeometry');
|
|
471
|
+
lines.push('70', '1');
|
|
472
|
+
for (const line of e.acisData)
|
|
473
|
+
lines.push('1', line);
|
|
474
|
+
return lines.join('\n');
|
|
475
|
+
}
|
|
476
|
+
function serializeMesh(e, ctx) {
|
|
477
|
+
const lines = ['0', 'MESH', ...entityCommon(e, ctx)];
|
|
478
|
+
lines.push('100', 'AcDbSubDMesh');
|
|
479
|
+
lines.push('71', '2'); // version
|
|
480
|
+
lines.push('72', String(e.subdivisionLevel));
|
|
481
|
+
lines.push('93', String(e.vertices.length));
|
|
482
|
+
for (const v of e.vertices)
|
|
483
|
+
lines.push('10', String(v[0]), '20', String(v[1]), '30', String(v[2]));
|
|
484
|
+
const faceData = [];
|
|
485
|
+
for (const f of e.faces) {
|
|
486
|
+
faceData.push(f.length, ...f);
|
|
487
|
+
}
|
|
488
|
+
lines.push('94', String(faceData.length));
|
|
489
|
+
for (const d of faceData)
|
|
490
|
+
lines.push('90', String(d));
|
|
491
|
+
lines.push('95', '0'); // edge count
|
|
492
|
+
lines.push('90', '0'); // edge crease count
|
|
493
|
+
return lines.join('\n');
|
|
494
|
+
}
|
|
495
|
+
function serializeMLeader(e, ctx) {
|
|
496
|
+
const lines = ['0', 'MULTILEADER', ...entityCommon(e, ctx)];
|
|
497
|
+
lines.push('100', 'AcDbMLeader');
|
|
498
|
+
lines.push('270', '2'); // version
|
|
499
|
+
lines.push('300', 'CONTEXT_DATA{');
|
|
500
|
+
lines.push('40', String(e.textHeight)); // text height
|
|
501
|
+
lines.push('10', String(e.textPosition[0]), '20', String(e.textPosition[1]), '30', '0');
|
|
502
|
+
lines.push('41', String(e.textWidth));
|
|
503
|
+
lines.push('304', e.text);
|
|
504
|
+
lines.push('301', '}');
|
|
505
|
+
// Leader line data
|
|
506
|
+
lines.push('302', 'LEADER{');
|
|
507
|
+
lines.push('290', '1'); // has set last leader line point
|
|
508
|
+
lines.push('291', '1'); // has set dogleg vector
|
|
509
|
+
lines.push('10', String(e.vertices[0]?.[0] || 0), '20', String(e.vertices[0]?.[1] || 0), '30', '0');
|
|
510
|
+
for (const v of e.vertices) {
|
|
511
|
+
lines.push('10', String(v[0]), '20', String(v[1]), '30', '0');
|
|
512
|
+
}
|
|
513
|
+
lines.push('303', '}');
|
|
514
|
+
// Style
|
|
515
|
+
lines.push('170', String(e.leaderType)); // leader type
|
|
516
|
+
lines.push('171', String(e.contentType)); // content type
|
|
517
|
+
lines.push('41', String(e.doglegLength)); // dogleg length
|
|
518
|
+
lines.push('40', String(e.arrowSize)); // arrow size
|
|
519
|
+
return lines.join('\n');
|
|
520
|
+
}
|
|
521
|
+
// ─── Table Records ───
|
|
522
|
+
function recordHead(type, handle, owner, subclass) {
|
|
523
|
+
return ['0', type, '5', handle, '330', owner, '100', 'AcDbSymbolTableRecord', '100', subclass];
|
|
524
|
+
}
|
|
525
|
+
/** LAYER table record. `plotStyle` / `material` are the document's default object handles. */
|
|
526
|
+
export function layerRecord(l, handle, owner, refs = {}) {
|
|
527
|
+
const color = Math.abs(l.color || 7);
|
|
528
|
+
const lines = recordHead('LAYER', handle, owner, 'AcDbLayerTableRecord');
|
|
529
|
+
lines.push('2', l.name);
|
|
530
|
+
lines.push('70', String((l.frozen ? 1 : 0) | (l.locked ? 4 : 0)));
|
|
531
|
+
lines.push('62', String(l.off ? -color : color));
|
|
532
|
+
lines.push('6', l.linetype || 'Continuous');
|
|
533
|
+
lines.push('370', '-3');
|
|
534
|
+
if (refs.plotStyle)
|
|
535
|
+
lines.push('390', refs.plotStyle);
|
|
536
|
+
if (refs.material)
|
|
537
|
+
lines.push('347', refs.material);
|
|
538
|
+
return lines.join('\n');
|
|
539
|
+
}
|
|
540
|
+
export function linetypeRecord(lt, handle, owner) {
|
|
541
|
+
const lines = recordHead('LTYPE', handle, owner, 'AcDbLinetypeTableRecord');
|
|
542
|
+
lines.push('2', lt.name);
|
|
543
|
+
lines.push('70', '0');
|
|
544
|
+
lines.push('3', lt.description);
|
|
545
|
+
lines.push('72', '65');
|
|
546
|
+
lines.push('73', String(lt.pattern.length));
|
|
547
|
+
lines.push('40', fmt(lt.pattern.reduce((sum, v) => sum + Math.abs(v), 0)));
|
|
548
|
+
for (const p of lt.pattern)
|
|
549
|
+
lines.push('49', fmt(p), '74', '0');
|
|
550
|
+
return lines.join('\n');
|
|
551
|
+
}
|
|
552
|
+
export function textStyleRecord(s, handle, owner) {
|
|
553
|
+
const lines = recordHead('STYLE', handle, owner, 'AcDbTextStyleTableRecord');
|
|
554
|
+
lines.push('2', s.name);
|
|
555
|
+
lines.push('70', '0');
|
|
556
|
+
lines.push('40', String(s.height || 0));
|
|
557
|
+
lines.push('41', '1');
|
|
558
|
+
lines.push('50', '0');
|
|
559
|
+
lines.push('71', '0');
|
|
560
|
+
lines.push('42', '2.5');
|
|
561
|
+
lines.push('3', s.font);
|
|
562
|
+
lines.push('4', '');
|
|
563
|
+
return lines.join('\n');
|
|
564
|
+
}
|
|
565
|
+
export function blockRecord(name, handle, owner, layoutHandle = '0') {
|
|
566
|
+
const lines = recordHead('BLOCK_RECORD', handle, owner, 'AcDbBlockTableRecord');
|
|
567
|
+
lines.push('2', name);
|
|
568
|
+
lines.push('340', layoutHandle);
|
|
569
|
+
lines.push('70', '0'); // insert units
|
|
570
|
+
lines.push('280', '1'); // explodable
|
|
571
|
+
lines.push('281', '0'); // scale uniformly: no
|
|
572
|
+
return lines.join('\n');
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* BLOCK … ENDBLK definition. `blockRecordHandle` owns the block and its content.
|
|
576
|
+
* Paper space layout blocks pass `paperSpace` so their content is flagged 67=1.
|
|
577
|
+
*/
|
|
578
|
+
export function blockDefinition(name, blockRecordHandle, basePoint, entities, ctx = {}) {
|
|
579
|
+
const paper = ctx.paperSpace ? ['67', '1'] : [];
|
|
580
|
+
const parts = [];
|
|
581
|
+
parts.push([
|
|
582
|
+
'0', 'BLOCK', '5', nextHandle(), '330', blockRecordHandle, '100', 'AcDbEntity', ...paper,
|
|
583
|
+
'8', '0', '100', 'AcDbBlockBegin', '2', name, '70', '0',
|
|
584
|
+
'10', String(basePoint[0]), '20', String(basePoint[1]), '30', '0', '3', name, '1', '',
|
|
585
|
+
].join('\n'));
|
|
586
|
+
const entCtx = { ...ctx, owner: blockRecordHandle };
|
|
587
|
+
for (const e of entities)
|
|
588
|
+
parts.push(serializeEntity(e, entCtx));
|
|
589
|
+
parts.push([
|
|
590
|
+
'0', 'ENDBLK', '5', nextHandle(), '330', blockRecordHandle, '100', 'AcDbEntity', ...paper,
|
|
591
|
+
'8', '0', '100', 'AcDbBlockEnd',
|
|
592
|
+
].join('\n'));
|
|
593
|
+
return parts.join('\n');
|
|
594
|
+
}
|
|
595
|
+
export { serializeEntity };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in hatch pattern definitions (ISO, millimetre based — as in acadiso.pat).
|
|
3
|
+
*
|
|
4
|
+
* Each line is `[angle, [baseX, baseY], [offsetX, offsetY], dashes]` exactly as
|
|
5
|
+
* stored in a DXF HATCH (group codes 53 / 43,44 / 45,46 / 49): the offset vector
|
|
6
|
+
* is already in world orientation. Scale/rotation are applied at serialization.
|
|
7
|
+
*
|
|
8
|
+
* A curated subset of ezdxf's ISO pattern table (MIT). Pass your own
|
|
9
|
+
* definition via `addHatch(..., { patternLines })` for anything not listed.
|
|
10
|
+
*
|
|
11
|
+
* GENERATED by tools/gen_hatch_patterns.py — do not edit by hand.
|
|
12
|
+
*/
|
|
13
|
+
/** One hatch pattern line: angle (deg), base point, offset vector, dash lengths. */
|
|
14
|
+
export type HatchPatternLine = [number, [number, number], [number, number], number[]];
|
|
15
|
+
export declare const HATCH_PATTERNS: Record<string, HatchPatternLine[]>;
|