@atlisp/mcp 1.8.15 → 1.8.17
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/dist/handlers/3d-handlers.js +128 -0
- package/dist/handlers/batch-handlers.js +260 -0
- package/dist/handlers/block-handlers.js +214 -0
- package/dist/handlers/cad-handlers.js +180 -0
- package/dist/handlers/constraint-handlers.js +130 -0
- package/dist/handlers/dim-hatch-handlers.js +340 -0
- package/dist/handlers/draw-handlers.js +175 -0
- package/dist/handlers/edit-handlers.js +179 -0
- package/dist/handlers/entity-handlers.js +196 -0
- package/dist/handlers/external-handlers.js +218 -0
- package/dist/handlers/file-handlers.js +172 -0
- package/dist/handlers/function-handlers.js +158 -0
- package/dist/handlers/layer-handlers.js +181 -0
- package/dist/handlers/package-handlers.js +89 -0
- package/dist/handlers/parametric-handlers.js +272 -0
- package/dist/handlers/pdf-annotation-handlers.js +117 -0
- package/dist/handlers/prompt-handlers.js +9 -0
- package/dist/handlers/purge-handlers.js +137 -0
- package/dist/handlers/query-print-handlers.js +662 -0
- package/dist/handlers/resource-cache.js +63 -0
- package/dist/handlers/resource-defs.js +79 -0
- package/dist/handlers/resource-handlers.js +131 -0
- package/dist/handlers/resource-readers.js +1045 -0
- package/dist/handlers/sheetset-handlers.js +430 -0
- package/dist/handlers/spatial-handlers.js +174 -0
- package/dist/handlers/style-handlers.js +162 -0
- package/dist/handlers/text-handlers.js +198 -0
- package/dist/handlers/ucs-layout-handlers.js +271 -0
- package/dist/handlers/viewport-handlers.js +150 -0
- package/dist/handlers/xdata-handlers.js +148 -0
- package/dist/handlers/xref-handlers.js +334 -0
- package/dist/lisp-security.js +7 -2
- package/package.json +2 -2
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
import { cad } from '../cad.js';
|
|
2
|
+
import { CAD_PLATFORMS, FILE_EXTENSIONS } from '../constants.js';
|
|
3
|
+
import { log } from '../logger.js';
|
|
4
|
+
import config from '../config.js';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { getCached, setCache } from './resource-cache.js';
|
|
8
|
+
import { STANDARDS_DIR, TBL_QUERIES, TBL_KEY_ALIAS } from './resource-defs.js';
|
|
9
|
+
import { parseLispList, parseLispRecursive, lispPairsToObject, escapeLispString } from '../handler-utils.js';
|
|
10
|
+
import { createError, ERROR_CODES } from '../errors.js';
|
|
11
|
+
|
|
12
|
+
const ENTITY_PROPERTIES_LISP = `
|
|
13
|
+
(defun entity:to-list (val)
|
|
14
|
+
(cond
|
|
15
|
+
((= (type val) 'VARIANT) (entity:to-list (vlax-variant-value val)))
|
|
16
|
+
((= (type val) 'SAFEARRAY) (vlax-safearray->list val))
|
|
17
|
+
(T val)))
|
|
18
|
+
|
|
19
|
+
(defun entity:try-get (obj names / result val)
|
|
20
|
+
(foreach name names
|
|
21
|
+
(if (not (vl-catch-all-error-p
|
|
22
|
+
(setq val (vl-catch-all-apply
|
|
23
|
+
(function vlax-get-property)
|
|
24
|
+
(list obj name)))))
|
|
25
|
+
(setq result (cons (cons name (entity:to-list val)) result))))
|
|
26
|
+
(reverse result))
|
|
27
|
+
|
|
28
|
+
(setq *common-props* '(ObjectName Handle Layer Color Linetype LinetypeScale Lineweight Visibility Material))
|
|
29
|
+
|
|
30
|
+
(setq *entity-props*
|
|
31
|
+
'((AcDbLine StartPoint EndPoint Angle Delta Length Thickness Normal)
|
|
32
|
+
(AcDbCircle Center Radius Diameter Circumference Area Thickness Normal)
|
|
33
|
+
(AcDbArc Center Radius StartAngle EndAngle TotalAngle ArcLength Area Normal)
|
|
34
|
+
(AcDbPoint Position Thickness Normal)
|
|
35
|
+
(AcDbText TextString Position Height Width ObliqueAngle Rotation StyleName HorizontalAlignment VerticalAlignment Normal Backward UpsideDown)
|
|
36
|
+
(AcDbMText TextString Location Height Width Rotation StyleName AttachmentPoint DrawingDirection LinespacingFactor LinespacingDistance LinespacingStyle FlowDirection)
|
|
37
|
+
(AcDbBlockReference Name InsertionPoint Rotation XScale YScale ZScale Normal)
|
|
38
|
+
(AcDbPolyline Coordinates Closed ConstantWidth Elevation Normal Thickness LinetypeGeneration Area Length)
|
|
39
|
+
(AcDb2dPolyline Coordinates Closed Elevation Normal Thickness LinetypeGeneration Area Length Type Width)
|
|
40
|
+
(AcDb3dPolyline Coordinates Closed Type Normal Length Area)
|
|
41
|
+
(AcDbAlignedDimension DimLinePoint1 DimLinePoint2 ExtLinePoint1 ExtLinePoint2 TextPosition TextRotation Measurement)
|
|
42
|
+
(AcDbRotatedDimension DimLinePoint1 DimLinePoint2 ExtLinePoint1 ExtLinePoint2 TextPosition TextRotation Measurement Rotation)
|
|
43
|
+
(AcDbRadialDimension Center Diameter LeaderLength TextPosition)
|
|
44
|
+
(AcDbDiametricDimension Center Diameter LeaderLength TextPosition)
|
|
45
|
+
(AcDbAngularDimension AngleVertex AngleEndPoint ArcPoint TextPosition Measurement)
|
|
46
|
+
(AcDbHatch PatternName PatternType Area Elevation Normal NumberOfLoops AssociativeHatch HatchObjectType PatternAngle PatternScale PatternSpace ISOPenWidth)
|
|
47
|
+
(AcDbSpline Degree NumberOfControlPoints NumberOfFitPoints ControlPoints FitPoints Knots Weights Area Length Closed Periodic StartTanVector EndTanVector Elevation Normal)
|
|
48
|
+
(AcDbEllipse Center MajorRadius MinorRadius RadiusRatio StartAngle EndAngle Area Circumference Normal)
|
|
49
|
+
(AcDbRegion Area Perimeter Centroid MomentsOfInertia PrincipalMoments PrincipalDirections Normal)
|
|
50
|
+
(AcDb3dSolid Volume Area Centroid MomentsOfInertia PrincipalMoments PrincipalDirections)
|
|
51
|
+
(AcDbRasterImage ImageFile ImageWidth ImageHeight Origin Rotation Width Height ShowImage ClippingEnabled)
|
|
52
|
+
(AcDbLeader Coordinates ArrowheadType ArrowheadSize Color ScaleFactor StyleName TextString TextPosition Type Normal)
|
|
53
|
+
(AcDbViewport Center Width Height ScaleFactor CustomScale StandardScale GridOn SnapOn On DisplayLocked)
|
|
54
|
+
(AcDbTable TableStyleName Position Rows Columns Direction FlowDirection)))
|
|
55
|
+
|
|
56
|
+
(defun entity:properties (ent / obj object-name result)
|
|
57
|
+
(setq obj (if (= (type ent) 'VLA-OBJECT) ent (vlax-ename->vla-object ent)))
|
|
58
|
+
(setq result (entity:try-get obj *common-props*))
|
|
59
|
+
(setq object-name (cdr (assoc 'ObjectName result)))
|
|
60
|
+
(if object-name
|
|
61
|
+
(setq result (append result (entity:try-get obj (cdr (assoc (read object-name) *entity-props*))))))
|
|
62
|
+
result)
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
function parseEntity(arr) {
|
|
66
|
+
if (!Array.isArray(arr) || arr.length < 5) return null;
|
|
67
|
+
const [type, handle, layer, color, linetype, ...rest] = arr;
|
|
68
|
+
const entity = {
|
|
69
|
+
type: String(type).toUpperCase().replace(/^"|"$/g, ''),
|
|
70
|
+
handle: String(handle),
|
|
71
|
+
layer: String(layer),
|
|
72
|
+
color,
|
|
73
|
+
linetype: String(linetype)
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const entType = typeof entity.type === 'string' ? entity.type.toUpperCase() : entity.type;
|
|
77
|
+
|
|
78
|
+
if (/^(TEXT|MTEXT|ATTRIB|TCH_TEXT)$/.test(entType) && rest[0] != null) {
|
|
79
|
+
entity.text = String(rest[0]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (/^(LINE|LWPOLYLINE|POLYLINE|ARC|CIRCLE|SPLINE|ELLIPSE|XLINE|RAY)$/.test(entType)
|
|
83
|
+
&& Array.isArray(rest[0])) {
|
|
84
|
+
entity.points = rest[0].map(pt => {
|
|
85
|
+
if (Array.isArray(pt) && pt.length >= 2)
|
|
86
|
+
return { x: Number(pt[0]), y: Number(pt[1]), z: Number(pt[2] || 0) };
|
|
87
|
+
return null;
|
|
88
|
+
}).filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (entType === 'INSERT' && rest[0] != null) {
|
|
92
|
+
entity.blockName = String(rest[0]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return entity;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseVlaEntity(obj) {
|
|
99
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
100
|
+
return {
|
|
101
|
+
type: obj.type || null,
|
|
102
|
+
handle: obj.handle || null,
|
|
103
|
+
layer: obj.layer || '0',
|
|
104
|
+
color: obj.color !== undefined ? obj.color : 256,
|
|
105
|
+
linetype: obj.linetype || 'ByLayer'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function propertyPairsToObject(pairs) {
|
|
110
|
+
if (!Array.isArray(pairs)) return null;
|
|
111
|
+
const obj = {};
|
|
112
|
+
for (const pair of pairs) {
|
|
113
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
114
|
+
obj[String(pair[0])] = pair[1] !== null && pair[1] !== undefined ? pair[1] : null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return obj;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildEntityPropertiesCode({ type, layer, bbox }) {
|
|
121
|
+
const items = [];
|
|
122
|
+
if (type) items.push(`(cons 0 "${escapeLispString(type)}")`);
|
|
123
|
+
if (layer) items.push(`(cons 8 "${escapeLispString(layer)}")`);
|
|
124
|
+
if (bbox) {
|
|
125
|
+
const parts = bbox.split(',').map(s => parseFloat(s.trim()));
|
|
126
|
+
if (parts.length === 4) {
|
|
127
|
+
const [x1, y1, x2, y2] = parts;
|
|
128
|
+
items.push(
|
|
129
|
+
`'(-4 . "<AND")`,
|
|
130
|
+
`'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
|
|
131
|
+
`'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
|
|
132
|
+
`'(-4 . "AND>")`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const filterExpr = items.length ? `(list ${items.join(' ')})` : 'nil';
|
|
137
|
+
const ssgetExpr = items.length ? `(ssget "_X" ${filterExpr})` : '(ssget "_X")';
|
|
138
|
+
|
|
139
|
+
return `(progn
|
|
140
|
+
(vl-load-com)
|
|
141
|
+
${ENTITY_PROPERTIES_LISP}
|
|
142
|
+
(setq @e:total (if (setq @e:ss ${ssgetExpr}) (sslength @e:ss) 0)
|
|
143
|
+
@e:ents nil @e:i 0)
|
|
144
|
+
(while (and @e:ss (< @e:i @e:total) (< (length @e:ents) 3000))
|
|
145
|
+
(setq @e:ents (cons (entity:properties (ssname @e:ss @e:i)) @e:ents)
|
|
146
|
+
@e:i (1+ @e:i)))
|
|
147
|
+
(vl-prin1-to-string (list @e:total (reverse @e:ents))))`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function ensureCad() {
|
|
151
|
+
if (!cad.connected) await cad.connect();
|
|
152
|
+
if (!cad.connected) throw createError(ERROR_CODES.CAD_NOT_CONNECTED);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function getEntityByHandle(handle) {
|
|
156
|
+
await ensureCad();
|
|
157
|
+
|
|
158
|
+
const code = `(progn
|
|
159
|
+
(vl-load-com)
|
|
160
|
+
(if (setq ent (handent "${escapeLispString(handle)}"))
|
|
161
|
+
(progn
|
|
162
|
+
(setq ed (entget ent))
|
|
163
|
+
(list
|
|
164
|
+
"type" (cdr (assoc 0 ed))
|
|
165
|
+
"handle" (cdr (assoc 5 ed))
|
|
166
|
+
"layer" (cdr (assoc 8 ed))
|
|
167
|
+
"color" (if (assoc 62 ed) (cdr (assoc 62 ed)) 256)
|
|
168
|
+
"linetype" (if (assoc 6 ed) (cdr (assoc 6 ed)) "ByLayer")
|
|
169
|
+
"elevation" (if (assoc 38 ed) (cdr (assoc 38 ed)) 0.0)
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
nil
|
|
173
|
+
)
|
|
174
|
+
)`;
|
|
175
|
+
try {
|
|
176
|
+
const result = await cad.sendCommandWithResult(code);
|
|
177
|
+
if (!result || result === 'nil') return { error: `未找到实体: ${handle}` };
|
|
178
|
+
try {
|
|
179
|
+
return JSON.parse(result);
|
|
180
|
+
} catch {
|
|
181
|
+
return { raw: result };
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
return { error: e.message };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function getCadInfo() {
|
|
189
|
+
const cached = getCached('cad:info');
|
|
190
|
+
if (cached) return cached;
|
|
191
|
+
|
|
192
|
+
if (!cad.connected) {
|
|
193
|
+
await cad.connect();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!cad.connected) {
|
|
197
|
+
return { connected: false, platform: null, version: null, busy: false };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let busy = false;
|
|
201
|
+
try {
|
|
202
|
+
busy = await cad.isBusy();
|
|
203
|
+
} catch (e) {
|
|
204
|
+
log(`Error checking CAD busy status: ${e.message}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result = {
|
|
208
|
+
connected: true,
|
|
209
|
+
platform: cad.getPlatform(),
|
|
210
|
+
version: cad.getVersion(),
|
|
211
|
+
busy
|
|
212
|
+
};
|
|
213
|
+
setCache('cad:info', result);
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function getEntities(params = {}) {
|
|
218
|
+
const cacheKey = `dwg:entities:${JSON.stringify(params)}`;
|
|
219
|
+
const cached = getCached(cacheKey);
|
|
220
|
+
if (cached) return cached;
|
|
221
|
+
|
|
222
|
+
await ensureCad();
|
|
223
|
+
|
|
224
|
+
const hasFilter = params.type || params.layer || params.bbox;
|
|
225
|
+
|
|
226
|
+
if (!hasFilter) {
|
|
227
|
+
return await getEntitiesSummary(cacheKey);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const code = buildEntityPropertiesCode({
|
|
232
|
+
type: params.type || null,
|
|
233
|
+
layer: params.layer || null,
|
|
234
|
+
bbox: params.bbox || null,
|
|
235
|
+
});
|
|
236
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
237
|
+
|
|
238
|
+
if (!resultStr || resultStr === '' || resultStr === 'nil') {
|
|
239
|
+
return { total: 0, entities: [] };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let total = 0;
|
|
243
|
+
let entities = [];
|
|
244
|
+
|
|
245
|
+
const parsed = parseLispRecursive(resultStr);
|
|
246
|
+
if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
|
|
247
|
+
total = typeof parsed[0] === 'number' ? parsed[0] : 0;
|
|
248
|
+
const rawEntities = Array.isArray(parsed[1]) ? parsed[1] : [];
|
|
249
|
+
entities = rawEntities.map(propertyPairsToObject).filter(Boolean);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const result = { total, entities };
|
|
253
|
+
|
|
254
|
+
log(`getEntities: total=${total}, returned=${entities.length}`);
|
|
255
|
+
setCache(cacheKey, result);
|
|
256
|
+
return result;
|
|
257
|
+
} catch (e) {
|
|
258
|
+
log(`getEntities error: ${e.message}`);
|
|
259
|
+
return { total: 0, entities: [] };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function getEntitiesSummary(cacheKey) {
|
|
264
|
+
const summaryCode = `(progn
|
|
265
|
+
(vl-load-com)
|
|
266
|
+
(setq ss (ssget "_X"))
|
|
267
|
+
(if ss
|
|
268
|
+
(progn
|
|
269
|
+
(setq total (sslength ss) i 0 typeCounts (list))
|
|
270
|
+
(while (< i total)
|
|
271
|
+
(setq ent (ssname ss i) ed (entget ent) typ (cdr (assoc 0 ed)))
|
|
272
|
+
(setq typeCounts (cons typ typeCounts) i (1+ i)))
|
|
273
|
+
(setq counts nil)
|
|
274
|
+
(foreach item typeCounts
|
|
275
|
+
(if (assoc item counts)
|
|
276
|
+
(setq counts (subst (cons item (1+ (cdr (assoc item counts)))) (assoc item counts) counts))
|
|
277
|
+
(setq counts (cons (cons item 1) counts))))
|
|
278
|
+
(cons (list "total" total) (reverse counts))
|
|
279
|
+
)
|
|
280
|
+
(list (list "total" 0))
|
|
281
|
+
)
|
|
282
|
+
)`;
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const resultStr = await cad.sendCommandWithResult(summaryCode);
|
|
286
|
+
|
|
287
|
+
if (!resultStr || resultStr === '' || resultStr === 'nil') {
|
|
288
|
+
return { total: 0, summary: [] };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const parsed = parseLispRecursive(resultStr);
|
|
292
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
293
|
+
return { total: 0, summary: [] };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let total = 0;
|
|
297
|
+
const summary = [];
|
|
298
|
+
|
|
299
|
+
for (const item of parsed) {
|
|
300
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
301
|
+
const key = String(item[0]);
|
|
302
|
+
const val = item[1];
|
|
303
|
+
if (key === 'total') {
|
|
304
|
+
total = typeof val === 'number' ? val : 0;
|
|
305
|
+
} else {
|
|
306
|
+
summary.push({ type: key, count: typeof val === 'number' ? val : 0 });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
summary.sort((a, b) => b.count - a.count);
|
|
312
|
+
|
|
313
|
+
const result = { total, summary };
|
|
314
|
+
log(`getEntitiesSummary: total=${total}, types=${summary.length}`);
|
|
315
|
+
setCache(cacheKey, result);
|
|
316
|
+
return result;
|
|
317
|
+
} catch (e) {
|
|
318
|
+
log(`getEntitiesSummary error: ${e.message}`);
|
|
319
|
+
return { total: 0, summary: [] };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function buildTextContentCode({ layer, bbox, offset, limit }) {
|
|
324
|
+
const items = [
|
|
325
|
+
`'(-4 . "<OR")`,
|
|
326
|
+
`'(0 . "TEXT")`,
|
|
327
|
+
`'(0 . "MTEXT")`,
|
|
328
|
+
`'(0 . "ATTRIB")`,
|
|
329
|
+
`'(-4 . "OR>")`,
|
|
330
|
+
];
|
|
331
|
+
if (layer) items.push(`(cons 8 "${escapeLispString(layer)}")`);
|
|
332
|
+
if (bbox) {
|
|
333
|
+
const parts = bbox.split(',').map(s => parseFloat(s.trim()));
|
|
334
|
+
if (parts.length === 4) {
|
|
335
|
+
const [x1, y1, x2, y2] = parts;
|
|
336
|
+
items.push(
|
|
337
|
+
`'(-4 . "<AND")`,
|
|
338
|
+
`'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
|
|
339
|
+
`'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
|
|
340
|
+
`'(-4 . "AND>")`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const filterExpr = `(list ${items.join(' ')})`;
|
|
345
|
+
|
|
346
|
+
return `(progn
|
|
347
|
+
(vl-load-com)
|
|
348
|
+
(setq ss (ssget "_X" ${filterExpr}))
|
|
349
|
+
(if (null ss)
|
|
350
|
+
(list "total" 0 "offset" ${offset} "limit" ${limit} "texts" nil)
|
|
351
|
+
(progn
|
|
352
|
+
(setq total (sslength ss) i ${offset} texts nil)
|
|
353
|
+
(while (and (< i total) (< (length texts) ${limit}))
|
|
354
|
+
(setq ent (ssname ss i) ed (entget ent) ins (cdr (assoc 10 ed)))
|
|
355
|
+
(setq typ (cdr (assoc 0 ed)) h (cdr (assoc 40 ed)))
|
|
356
|
+
(if (null h) (setq h 2.5))
|
|
357
|
+
(setq texts (append texts (list (list
|
|
358
|
+
"text" (vl-prin1-to-string (cdr (assoc 1 ed)))
|
|
359
|
+
"position" (list (car ins) (cadr ins) (caddr ins))
|
|
360
|
+
"height" h
|
|
361
|
+
"handle" (cdr (assoc 5 ed))
|
|
362
|
+
))) i (1+ i)))
|
|
363
|
+
(list "total" total "offset" ${offset} "limit" ${limit} "texts" texts))))
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function getTextContent(params = {}) {
|
|
368
|
+
const cacheKey = `dwg:text-content:${JSON.stringify(params)}`;
|
|
369
|
+
const cached = getCached(cacheKey);
|
|
370
|
+
if (cached) return cached;
|
|
371
|
+
|
|
372
|
+
await ensureCad();
|
|
373
|
+
|
|
374
|
+
const offset = Math.max(0, parseInt(params.offset) || 0);
|
|
375
|
+
const limit = Math.min(Math.max(1, parseInt(params.limit) || 5000), 5000);
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const code = buildTextContentCode({ layer: params.layer, bbox: params.bbox, offset, limit });
|
|
379
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
380
|
+
|
|
381
|
+
log(`[DEBUG text-content] resultStr length=${resultStr?.length} first300=${resultStr?.substring(0, 300)}`);
|
|
382
|
+
|
|
383
|
+
const parsed = parseLispRecursive(resultStr);
|
|
384
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
385
|
+
throw new Error(`text-content: 解析 Lisp 结果失败: raw="${String(resultStr).substring(0, 500)}"`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const data = {};
|
|
389
|
+
let rawTexts = [];
|
|
390
|
+
for (let i = 0; i < parsed.length - 1; i += 2) {
|
|
391
|
+
const key = parsed[i];
|
|
392
|
+
const val = parsed[i + 1];
|
|
393
|
+
if (typeof key === 'string' || typeof key === 'number') {
|
|
394
|
+
if (key === 'texts' && Array.isArray(val)) {
|
|
395
|
+
rawTexts = val;
|
|
396
|
+
} else {
|
|
397
|
+
data[String(key)] = val;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
data.texts = rawTexts.map(item => {
|
|
402
|
+
if (!Array.isArray(item)) return null;
|
|
403
|
+
const obj = {};
|
|
404
|
+
for (let i = 0; i < item.length - 1; i += 2) {
|
|
405
|
+
const key = item[i];
|
|
406
|
+
const val = item[i + 1];
|
|
407
|
+
if (key === 'text') obj.text = String(val || '');
|
|
408
|
+
else if (key === 'position') {
|
|
409
|
+
obj.position = Array.isArray(val) ? val.map(v => typeof v === 'number' ? v : 0) : [];
|
|
410
|
+
}
|
|
411
|
+
else if (key === 'height') obj.height = typeof val === 'number' ? val : 2.5;
|
|
412
|
+
else if (key === 'handle') obj.handle = String(val || '');
|
|
413
|
+
}
|
|
414
|
+
return obj.text ? obj : null;
|
|
415
|
+
}).filter(Boolean);
|
|
416
|
+
|
|
417
|
+
setCache(cacheKey, data);
|
|
418
|
+
return data;
|
|
419
|
+
} catch (e) {
|
|
420
|
+
log(`getTextContent error: ${e.message}`);
|
|
421
|
+
throw e;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function medianHeight(texts) {
|
|
426
|
+
const heights = texts.filter(t => t.height > 0).map(t => t.height);
|
|
427
|
+
if (heights.length === 0) return 2.5;
|
|
428
|
+
heights.sort((a, b) => a - b);
|
|
429
|
+
const mid = Math.floor(heights.length / 2);
|
|
430
|
+
return heights.length % 2 !== 0 ? heights[mid] : (heights[mid - 1] + heights[mid]) / 2;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function shouldContinueLine(prevText, nextText) {
|
|
434
|
+
if (!prevText || !nextText) return false;
|
|
435
|
+
const trimmed = prevText.trimEnd();
|
|
436
|
+
if (trimmed.length === 0) return false;
|
|
437
|
+
const lastChar = trimmed[trimmed.length - 1];
|
|
438
|
+
const sentenceEnd = '.!?。!?';
|
|
439
|
+
if (sentenceEnd.includes(lastChar)) return false;
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function combineText(params = {}) {
|
|
444
|
+
const content = await getTextContent(params);
|
|
445
|
+
const texts = content.texts || [];
|
|
446
|
+
if (texts.length === 0) return { total: 0, blocks: [], medianHeight: 2.5 };
|
|
447
|
+
|
|
448
|
+
const mh = medianHeight(texts);
|
|
449
|
+
const yTolerance = params.yTolerance || mh * 1.2;
|
|
450
|
+
const xGapRatio = mh * 3.0;
|
|
451
|
+
const blockSpacing = mh * 2.5;
|
|
452
|
+
|
|
453
|
+
const rows = [];
|
|
454
|
+
for (const item of texts) {
|
|
455
|
+
if (!item.text) continue;
|
|
456
|
+
const [x, y] = item.position || [0, 0];
|
|
457
|
+
let matched = null;
|
|
458
|
+
for (const row of rows) {
|
|
459
|
+
if (Math.abs(row.y - y) <= yTolerance) { matched = row; break; }
|
|
460
|
+
}
|
|
461
|
+
if (matched) {
|
|
462
|
+
matched.items.push({ x, text: item.text, height: item.height || mh });
|
|
463
|
+
} else {
|
|
464
|
+
rows.push({ y, items: [{ x, text: item.text, height: item.height || mh }] });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
for (const row of rows) {
|
|
469
|
+
row.items.sort((a, b) => a.x - b.x);
|
|
470
|
+
row.lines = [];
|
|
471
|
+
let cur = [];
|
|
472
|
+
for (const item of row.items) {
|
|
473
|
+
if (cur.length === 0) { cur.push(item); continue; }
|
|
474
|
+
const prev = cur[cur.length - 1];
|
|
475
|
+
const localH = prev.height || mh;
|
|
476
|
+
if (item.x - prev.x > xGapRatio) {
|
|
477
|
+
row.lines.push(cur.map(t => t.text).join(''));
|
|
478
|
+
cur = [item];
|
|
479
|
+
} else {
|
|
480
|
+
cur.push(item);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (cur.length > 0) row.lines.push(cur.map(t => t.text).join(''));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
rows.sort((a, b) => a.y - b.y);
|
|
487
|
+
const rawBlocks = [];
|
|
488
|
+
let curBlock = null;
|
|
489
|
+
for (const row of rows) {
|
|
490
|
+
if (!curBlock) {
|
|
491
|
+
curBlock = { rows: [row] };
|
|
492
|
+
} else {
|
|
493
|
+
const last = curBlock.rows[curBlock.rows.length - 1];
|
|
494
|
+
if (row.y - last.y > blockSpacing) {
|
|
495
|
+
rawBlocks.push(curBlock);
|
|
496
|
+
curBlock = { rows: [row] };
|
|
497
|
+
} else {
|
|
498
|
+
curBlock.rows.push(row);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (curBlock) rawBlocks.push(curBlock);
|
|
503
|
+
|
|
504
|
+
const blocks = rawBlocks.map(block => {
|
|
505
|
+
const orderedRows = [...block.rows].reverse();
|
|
506
|
+
const lines = [];
|
|
507
|
+
for (let i = 0; i < orderedRows.length; i++) {
|
|
508
|
+
const rowText = orderedRows[i].lines.join(' ');
|
|
509
|
+
if (i === 0) {
|
|
510
|
+
lines.push(rowText);
|
|
511
|
+
} else {
|
|
512
|
+
const prevLine = lines[lines.length - 1];
|
|
513
|
+
if (shouldContinueLine(prevLine, rowText)) {
|
|
514
|
+
lines[lines.length - 1] = prevLine + rowText;
|
|
515
|
+
} else {
|
|
516
|
+
lines.push(rowText);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
text: lines.join('\n'),
|
|
522
|
+
y: orderedRows[0].y,
|
|
523
|
+
rowCount: orderedRows.length,
|
|
524
|
+
lines
|
|
525
|
+
};
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
blocks.sort((a, b) => b.y - a.y);
|
|
529
|
+
return { total: blocks.length, blocks, medianHeight: mh };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async function getDwgName() {
|
|
533
|
+
await ensureCad();
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
const code = `(vl-princ-to-string (getvar "dwgname"))`;
|
|
537
|
+
const name = await cad.sendCommandWithResult(code);
|
|
538
|
+
return { name: name || null };
|
|
539
|
+
} catch (e) {
|
|
540
|
+
return { name: null };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function getDwgPath() {
|
|
545
|
+
await ensureCad();
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
const code = `(progn
|
|
549
|
+
(setq prefix (vl-princ-to-string (getvar "dwgprefix")))
|
|
550
|
+
(setq name (vl-princ-to-string (getvar "dwgname")))
|
|
551
|
+
(list (strcat prefix name) name)
|
|
552
|
+
)`;
|
|
553
|
+
const result = await cad.sendCommandWithResult(code);
|
|
554
|
+
|
|
555
|
+
if (!result || result === "nil" || result === "") {
|
|
556
|
+
return { path: null, name: null };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
let parsed = null;
|
|
560
|
+
try { parsed = JSON.parse(result); } catch (e) { log(`resource-readers: JSON parse error: ${e.message}`); }
|
|
561
|
+
if (!parsed) {
|
|
562
|
+
const listResult = parseLispList(result);
|
|
563
|
+
if (listResult && listResult.length >= 2) parsed = listResult;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
|
|
567
|
+
return { path: String(parsed[0]), name: String(parsed[1]) };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return { path: result, name: result };
|
|
571
|
+
} catch (e) {
|
|
572
|
+
return { path: null, name: null };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function buildBlockRefsCode(filterBlockName) {
|
|
577
|
+
const filterItems = ['(cons 0 "INSERT")'];
|
|
578
|
+
if (filterBlockName) {
|
|
579
|
+
filterItems.push(`(cons 2 "${escapeLispString(filterBlockName)}")`);
|
|
580
|
+
}
|
|
581
|
+
const filterCode = `(list ${filterItems.join(' ')})`;
|
|
582
|
+
return `(progn
|
|
583
|
+
(vl-load-com)
|
|
584
|
+
|
|
585
|
+
(defun @br:try-fn (fn arg / r)
|
|
586
|
+
(setq r (vl-catch-all-apply (function (lambda () (apply fn (list arg))))))
|
|
587
|
+
(if (vl-catch-all-error-p r) nil r))
|
|
588
|
+
|
|
589
|
+
(defun @br:props (ent / ed r p2 p10 p50 p41 p42 p43)
|
|
590
|
+
(setq ed (entget ent)
|
|
591
|
+
p10 (cdr (assoc 10 ed))
|
|
592
|
+
p50 (cdr (assoc 50 ed))
|
|
593
|
+
p41 (cdr (assoc 41 ed))
|
|
594
|
+
p42 (cdr (assoc 42 ed))
|
|
595
|
+
p43 (cdr (assoc 43 ed)))
|
|
596
|
+
(setq r (vl-catch-all-apply
|
|
597
|
+
(function (lambda ()
|
|
598
|
+
(list
|
|
599
|
+
(cons "handle" (cdr (assoc 5 ed)))
|
|
600
|
+
(cons "layer" (cdr (assoc 8 ed)))
|
|
601
|
+
(cons "blockName" (cdr (assoc 2 ed)))
|
|
602
|
+
(cons "effectiveBlockName" (cdr (assoc 2 ed)))
|
|
603
|
+
(cons "insertionPoint" (list (car p10) (cadr p10) (caddr p10)))
|
|
604
|
+
(cons "rotation" p50)
|
|
605
|
+
(cons "scale" (list p41 p42 p43))
|
|
606
|
+
(cons "attributes" (@br:try-fn 'block:get-attributes ent))
|
|
607
|
+
(cons "dynamicProperties" (@br:try-fn 'block:get-properties ent)))))))
|
|
608
|
+
(if (vl-catch-all-error-p r) (list (cons "handle" (cdr (assoc 5 ed)))) r))
|
|
609
|
+
|
|
610
|
+
(setq ss (ssget "_X" ${filterCode}))
|
|
611
|
+
(if (null ss)
|
|
612
|
+
(list (cons "total" 0) (cons "blockRefs" nil))
|
|
613
|
+
(progn
|
|
614
|
+
(setq total (sslength ss) i 0 refs nil)
|
|
615
|
+
(while (< i total)
|
|
616
|
+
(setq r (@br:props (ssname ss i)))
|
|
617
|
+
(if r (setq refs (cons r refs)))
|
|
618
|
+
(setq i (1+ i)))
|
|
619
|
+
(list (cons "total" total) (cons "blockRefs" (list (reverse refs)))))))`;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async function getBlockRefs(params = {}) {
|
|
623
|
+
const cacheKey = `dwg:block-refs:${params.blockName || 'all'}`;
|
|
624
|
+
const cached = getCached(cacheKey);
|
|
625
|
+
if (cached) return cached;
|
|
626
|
+
|
|
627
|
+
await ensureCad();
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
const code = buildBlockRefsCode(params.blockName);
|
|
631
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
632
|
+
|
|
633
|
+
const parsed = parseLispRecursive(resultStr);
|
|
634
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
635
|
+
throw new Error(`block-refs: 解析 Lisp 结果失败: raw="${String(resultStr).substring(0, 500)}"`);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const result = {};
|
|
639
|
+
for (const pair of parsed) {
|
|
640
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
641
|
+
result[String(pair[0])] = pair[1];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const blockRefs = Array.isArray(result.blockRefs) ? result.blockRefs : [];
|
|
645
|
+
const finalResult = {
|
|
646
|
+
total: typeof result.total === 'number' ? result.total : blockRefs.length,
|
|
647
|
+
blockRefs: blockRefs.map(br => {
|
|
648
|
+
const obj = {};
|
|
649
|
+
if (Array.isArray(br)) {
|
|
650
|
+
br.forEach(pair => {
|
|
651
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
652
|
+
const key = pair[0];
|
|
653
|
+
let val = pair.length === 2 ? pair[1] : pair.slice(1);
|
|
654
|
+
if (key === 'insertionPoint' && Array.isArray(val)) {
|
|
655
|
+
val = val.map(v => typeof v === 'number' ? v : 0);
|
|
656
|
+
} else if (key === 'scale' && Array.isArray(val)) {
|
|
657
|
+
val = val.map(v => typeof v === 'number' ? v : 1);
|
|
658
|
+
} else if (key === 'isDynamic') {
|
|
659
|
+
val = val === true || val === 'true' || val === 1;
|
|
660
|
+
} else if (key === 'attributes' && Array.isArray(val)) {
|
|
661
|
+
const o = {};
|
|
662
|
+
val.forEach(p => { if (Array.isArray(p) && p.length >= 2) o[String(p[0])] = p[1]; });
|
|
663
|
+
val = o;
|
|
664
|
+
} else if (key === 'dynamicProperties' && Array.isArray(val)) {
|
|
665
|
+
const o = {};
|
|
666
|
+
val.forEach(p => { if (Array.isArray(p) && p.length >= 2) o[String(p[0])] = p[1]; });
|
|
667
|
+
val = o;
|
|
668
|
+
}
|
|
669
|
+
obj[key] = val;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
return obj;
|
|
674
|
+
})
|
|
675
|
+
};
|
|
676
|
+
setCache(cacheKey, finalResult);
|
|
677
|
+
return finalResult;
|
|
678
|
+
} catch (e) {
|
|
679
|
+
log(`getBlockRefs error: ${e.message}`);
|
|
680
|
+
throw e;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const VLA_HELPER_LISP = `(defun @:get-props (obj prop-names / result p p-sym val)
|
|
685
|
+
(setq result nil)
|
|
686
|
+
(foreach p prop-names
|
|
687
|
+
(setq p-sym (read p))
|
|
688
|
+
(if (vlax-property-available-p obj p-sym)
|
|
689
|
+
(progn
|
|
690
|
+
(setq val (vl-catch-all-apply 'vlax-get (list obj p-sym)))
|
|
691
|
+
(if (not (vl-catch-all-error-p val))
|
|
692
|
+
(progn
|
|
693
|
+
(cond
|
|
694
|
+
((= (type val) 'VLA-OBJECT) (setq val (strcat "@" (vla-get-objectname val))))
|
|
695
|
+
((or (= (type val) 'STR) (= (type val) 'SAFEARRAY) (listp val)) (setq val (vl-prin1-to-string val))))
|
|
696
|
+
(setq result (cons (cons p val) result)))))))
|
|
697
|
+
(reverse result))`;
|
|
698
|
+
|
|
699
|
+
function buildTablesCode(tblName) {
|
|
700
|
+
const raw = tblName.toLowerCase();
|
|
701
|
+
const key = TBL_KEY_ALIAS[raw] || raw;
|
|
702
|
+
const query = TBL_QUERIES[key];
|
|
703
|
+
const isVlaTable = key !== 'layer';
|
|
704
|
+
if (query) {
|
|
705
|
+
if (isVlaTable) {
|
|
706
|
+
return `(progn (vl-load-com) ${VLA_HELPER_LISP} ${query.lisp})`;
|
|
707
|
+
}
|
|
708
|
+
return `(progn (vl-load-com) ${query.lisp})`;
|
|
709
|
+
}
|
|
710
|
+
const parts = Object.values(TBL_QUERIES).map(q => `(vl-catch-all-apply '(lambda () ${q.lisp}))`);
|
|
711
|
+
return `(progn (vl-load-com) ${VLA_HELPER_LISP} (list ${parts.join(' ')}))`;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function parseTableRecord(rec, tableName) {
|
|
715
|
+
if (!rec || !Array.isArray(rec)) return null;
|
|
716
|
+
const obj = {};
|
|
717
|
+
const isLayer = tableName === 'layer';
|
|
718
|
+
for (const pair of rec) {
|
|
719
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
720
|
+
const key = String(pair[0]);
|
|
721
|
+
const val = pair[1];
|
|
722
|
+
const isDxfCode = typeof pair[0] === 'number';
|
|
723
|
+
if (isDxfCode) {
|
|
724
|
+
if (key === '2') obj.name = String(val);
|
|
725
|
+
else if (key === '62') obj.color = typeof val === 'number' ? val : 7;
|
|
726
|
+
else if (key === '6') obj.linetype = String(val);
|
|
727
|
+
else if (key === '70') {
|
|
728
|
+
obj.flags = typeof val === 'number' ? val : 0;
|
|
729
|
+
if (isLayer) {
|
|
730
|
+
obj.frozen = (val & 1) !== 0;
|
|
731
|
+
obj.locked = (val & 4) !== 0;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
else if (key === '60') obj.on = val === 0;
|
|
735
|
+
else if (key === '1') obj.font = String(val);
|
|
736
|
+
else if (key === '40') obj.height = typeof val === 'number' ? val : 0;
|
|
737
|
+
else if (key === '41') obj.width = typeof val === 'number' ? val : 1;
|
|
738
|
+
else if (key === '42') obj.oblique = typeof val === 'number' ? val : 0;
|
|
739
|
+
else if (key === '3') obj.bigfont = String(val);
|
|
740
|
+
else if (key === '50') obj.flags = typeof val === 'number' ? val : 0;
|
|
741
|
+
} else {
|
|
742
|
+
if (key === 'Name') obj.name = val;
|
|
743
|
+
obj[key] = val;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return obj.name ? obj : null;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function getTables(params = {}) {
|
|
751
|
+
const tblName = params.tbl || params.type || 'all';
|
|
752
|
+
const cacheKey = `dwg:tbl:${tblName}`;
|
|
753
|
+
const cached = getCached(cacheKey);
|
|
754
|
+
if (cached) return cached;
|
|
755
|
+
|
|
756
|
+
await ensureCad();
|
|
757
|
+
|
|
758
|
+
try {
|
|
759
|
+
const code = buildTablesCode(tblName);
|
|
760
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
761
|
+
|
|
762
|
+
if (!resultStr || resultStr === '' || resultStr === 'nil') {
|
|
763
|
+
return { table: tblName, data: [] };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const parsed = parseLispRecursive(resultStr);
|
|
767
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
768
|
+
return { table: tblName, total: 0, data: [] };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
let result;
|
|
773
|
+
if (parsed.length >= 3 && parsed[0] && String(parsed[0][0]).toUpperCase() === 'TABLE') {
|
|
774
|
+
const tableName = String(parsed[0][1]).toLowerCase();
|
|
775
|
+
let total = 0;
|
|
776
|
+
let tblData = [];
|
|
777
|
+
for (const item of parsed) {
|
|
778
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
779
|
+
const key = String(item[0]).toUpperCase();
|
|
780
|
+
if (key === 'TOTAL') total = typeof item[1] === 'number' ? item[1] : 0;
|
|
781
|
+
else if (key === 'DATA') {
|
|
782
|
+
if (Array.isArray(item[1]) && item[1].length > 0) {
|
|
783
|
+
tblData = item[1];
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (tblData.length === 0 && parsed.length > 2) {
|
|
789
|
+
tblData = parsed.slice(2);
|
|
790
|
+
}
|
|
791
|
+
result = {
|
|
792
|
+
table: tableName,
|
|
793
|
+
total: total,
|
|
794
|
+
data: tblData.map(r => parseTableRecord(r, tableName)).filter(Boolean)
|
|
795
|
+
};
|
|
796
|
+
} else {
|
|
797
|
+
const allData = {};
|
|
798
|
+
parsed.forEach(item => {
|
|
799
|
+
if (!Array.isArray(item) || item.length < 2) return;
|
|
800
|
+
let tableKey = null;
|
|
801
|
+
const entry = { total: 0, data: [] };
|
|
802
|
+
if (Array.isArray(item[0]) && item[0].length >= 2 && String(item[0][0]).toLowerCase() === 'table') {
|
|
803
|
+
tableKey = String(item[0][1]).toLowerCase();
|
|
804
|
+
for (let i = 1; i < item.length; i++) {
|
|
805
|
+
const sub = item[i];
|
|
806
|
+
if (!Array.isArray(sub) || sub.length < 2) continue;
|
|
807
|
+
const subKey = String(sub[0]).toUpperCase();
|
|
808
|
+
if (subKey === 'TOTAL') entry.total = typeof sub[1] === 'number' ? sub[1] : 0;
|
|
809
|
+
else if (subKey === 'DATA') {
|
|
810
|
+
const raw = Array.isArray(sub[1]) ? sub[1] : sub.slice(1);
|
|
811
|
+
entry.data = raw.map(r => parseTableRecord(r, tableKey)).filter(Boolean);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
} else {
|
|
815
|
+
tableKey = String(item[0]).toLowerCase();
|
|
816
|
+
for (let i = 1; i < item.length; i++) {
|
|
817
|
+
const sub = item[i];
|
|
818
|
+
if (!Array.isArray(sub) || sub.length < 2) continue;
|
|
819
|
+
const subKey = String(sub[0]).toUpperCase();
|
|
820
|
+
if (subKey === 'TOTAL') {
|
|
821
|
+
entry.total = typeof sub[1] === 'number' ? sub[1] : 0;
|
|
822
|
+
} else if (subKey === 'DATA') {
|
|
823
|
+
const raw = Array.isArray(sub[1]) ? sub[1] : sub.slice(1);
|
|
824
|
+
entry.data = raw.map(r => parseTableRecord(r, tableKey)).filter(Boolean);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (tableKey) allData[tableKey] = entry;
|
|
829
|
+
});
|
|
830
|
+
result = allData;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
setCache(cacheKey, result);
|
|
834
|
+
return result;
|
|
835
|
+
} catch (e) {
|
|
836
|
+
log(`getTables error: ${e.message}`);
|
|
837
|
+
return { table: tblName, data: [] };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async function getPackages(filterName = null) {
|
|
842
|
+
const cacheKey = filterName ? `packages:${filterName}` : 'packages:all';
|
|
843
|
+
const cached = getCached(cacheKey);
|
|
844
|
+
if (cached) return cached;
|
|
845
|
+
|
|
846
|
+
await ensureCad();
|
|
847
|
+
|
|
848
|
+
try {
|
|
849
|
+
const code = `(mapcar 'cdr @::*local-pkgs*)`;
|
|
850
|
+
const result = await cad.sendCommandWithResult(code);
|
|
851
|
+
|
|
852
|
+
if (!result || result === '' || result === 'nil') {
|
|
853
|
+
return [];
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
let raw = [];
|
|
857
|
+
try {
|
|
858
|
+
raw = JSON.parse(result);
|
|
859
|
+
} catch {
|
|
860
|
+
raw = parseLispList(result) || [];
|
|
861
|
+
}
|
|
862
|
+
if (!Array.isArray(raw)) raw = [];
|
|
863
|
+
|
|
864
|
+
const packages = raw.map(item => {
|
|
865
|
+
if (Array.isArray(item)) {
|
|
866
|
+
const pkg = { name: '', version: '0.0.0', description: '', loaded: true };
|
|
867
|
+
for (const entry of item) {
|
|
868
|
+
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
869
|
+
const [[key, val]] = Object.entries(entry);
|
|
870
|
+
const k = String(key);
|
|
871
|
+
const v = String(val);
|
|
872
|
+
if (k === ':NAME') pkg.name = v;
|
|
873
|
+
else if (k === ':VERSION') pkg.version = v;
|
|
874
|
+
else if (k === ':DESCRIPTION') pkg.description = v;
|
|
875
|
+
else if (k === ':FULL-NAME') pkg.fullName = v;
|
|
876
|
+
else if (k === ':AUTHOR') pkg.author = v;
|
|
877
|
+
else if (k === ':CATEGORY') pkg.category = v;
|
|
878
|
+
else if (k === ':URL') pkg.url = v;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return pkg;
|
|
882
|
+
}
|
|
883
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
884
|
+
return { name: String(item), version: '0.0.0', loaded: true };
|
|
885
|
+
}
|
|
886
|
+
if (item && typeof item === 'object') {
|
|
887
|
+
const pkg = { name: '', version: '0.0.0', description: '', loaded: true };
|
|
888
|
+
const nameVal = item.name || item.Name || item.NAME || item[':NAME'];
|
|
889
|
+
if (nameVal) pkg.name = String(nameVal);
|
|
890
|
+
const verVal = item.version || item.Version || item.VERSION || item[':VERSION'];
|
|
891
|
+
if (verVal) pkg.version = String(verVal);
|
|
892
|
+
const descVal = item.description || item.Description || item.DESCRIPTION || item[':DESCRIPTION'];
|
|
893
|
+
if (descVal) pkg.description = String(descVal);
|
|
894
|
+
if (item.fullName || item.full_name) pkg.fullName = String(item.fullName || item.full_name);
|
|
895
|
+
if (item.author || item.Author) pkg.author = String(item.author || item.Author);
|
|
896
|
+
if (item.category || item.Category) pkg.category = String(item.category || item.Category);
|
|
897
|
+
if (item.url || item.Url || item.URL) pkg.url = String(item.url || item.Url || item.URL);
|
|
898
|
+
return pkg;
|
|
899
|
+
}
|
|
900
|
+
return null;
|
|
901
|
+
}).filter(Boolean);
|
|
902
|
+
|
|
903
|
+
if (filterName) {
|
|
904
|
+
const filtered = packages.filter(p => p.name === filterName);
|
|
905
|
+
setCache(cacheKey, filtered);
|
|
906
|
+
return filtered;
|
|
907
|
+
}
|
|
908
|
+
setCache(cacheKey, packages);
|
|
909
|
+
return packages;
|
|
910
|
+
} catch (e) {
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function getPlatforms() {
|
|
916
|
+
return CAD_PLATFORMS.map(name => ({
|
|
917
|
+
name,
|
|
918
|
+
extensions: FILE_EXTENSIONS[name] || []
|
|
919
|
+
}));
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async function getDwgList() {
|
|
923
|
+
const cacheKey = 'cad:dwgs';
|
|
924
|
+
const cached = getCached(cacheKey);
|
|
925
|
+
if (cached) return cached;
|
|
926
|
+
|
|
927
|
+
await ensureCad();
|
|
928
|
+
|
|
929
|
+
try {
|
|
930
|
+
const code = `(progn
|
|
931
|
+
(vl-load-com)
|
|
932
|
+
(setq result nil)
|
|
933
|
+
(vlax-for doc (vla-get-Documents (vla-get-Application (vlax-get-acad-object)))
|
|
934
|
+
(setq result (cons (list (vla-get-Name doc) (vla-get-FullName doc)) result))
|
|
935
|
+
)
|
|
936
|
+
(reverse result)
|
|
937
|
+
)`;
|
|
938
|
+
const result = await cad.sendCommandWithResult(code);
|
|
939
|
+
|
|
940
|
+
if (!result || result === "" || result === "nil") {
|
|
941
|
+
return [];
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
let parsed = [];
|
|
945
|
+
try {
|
|
946
|
+
parsed = JSON.parse(result);
|
|
947
|
+
} catch (e) {
|
|
948
|
+
const listResult = parseLispRecursive(result);
|
|
949
|
+
if (Array.isArray(listResult)) {
|
|
950
|
+
parsed = listResult;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (!Array.isArray(parsed)) parsed = [];
|
|
955
|
+
|
|
956
|
+
const dwgList = parsed.map(item => {
|
|
957
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
958
|
+
return { name: String(item[0]), path: String(item[1]) };
|
|
959
|
+
}
|
|
960
|
+
return null;
|
|
961
|
+
}).filter(Boolean);
|
|
962
|
+
|
|
963
|
+
setCache(cacheKey, dwgList);
|
|
964
|
+
return dwgList;
|
|
965
|
+
} catch (e) {
|
|
966
|
+
return [];
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function loadStandardsFile(filename) {
|
|
971
|
+
const filePath = path.join(STANDARDS_DIR, filename);
|
|
972
|
+
try {
|
|
973
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
974
|
+
return JSON.parse(raw);
|
|
975
|
+
} catch (e) {
|
|
976
|
+
log(`Error loading standards file ${filename}: ${e.message}`);
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const standardsCache = new Map();
|
|
982
|
+
|
|
983
|
+
function getCachedStandards(key) {
|
|
984
|
+
const entry = standardsCache.get(key);
|
|
985
|
+
if (entry && Date.now() - entry.timestamp < 30000) return entry.data;
|
|
986
|
+
standardsCache.delete(key);
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function setCachedStandards(key, data) {
|
|
991
|
+
standardsCache.set(key, { data, timestamp: Date.now() });
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function getDraftingStandards() {
|
|
995
|
+
const cached = getCachedStandards('drafting');
|
|
996
|
+
if (cached) return cached;
|
|
997
|
+
const data = loadStandardsFile('drafting.json');
|
|
998
|
+
if (data) setCachedStandards('drafting', data);
|
|
999
|
+
return data || { error: '制图规范文件加载失败' };
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function getCodingStandards() {
|
|
1003
|
+
const cached = getCachedStandards('coding');
|
|
1004
|
+
if (cached) return cached;
|
|
1005
|
+
const data = loadStandardsFile('coding.json');
|
|
1006
|
+
if (data) setCachedStandards('coding', data);
|
|
1007
|
+
return data || { error: '编码规范文件加载失败' };
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
async function getCadEvents(params) {
|
|
1011
|
+
await ensureCad();
|
|
1012
|
+
try {
|
|
1013
|
+
const result = await cad.sendCommandWithResult('(progn (setq cnt 0) (if (setq ss (ssget "_X")) (setq cnt (sslength ss))) (list (cons "entityCount" cnt) (cons "dwgName" (getvar "dwgname")) (cons "dwgPath" (getvar "dwgprefix"))))');
|
|
1014
|
+
const data = {};
|
|
1015
|
+
try {
|
|
1016
|
+
const parsed = JSON.parse(result);
|
|
1017
|
+
if (Array.isArray(parsed)) {
|
|
1018
|
+
for (const pair of parsed) {
|
|
1019
|
+
if (Array.isArray(pair) && pair.length >= 2) data[String(pair[0])] = pair[1];
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
} catch { data.raw = result; }
|
|
1023
|
+
return { entityCount: data.entityCount || 0, dwgName: data.dwgName || '', dwgPath: data.dwgPath || '', timestamp: new Date().toISOString() };
|
|
1024
|
+
} catch (e) {
|
|
1025
|
+
return { entityCount: 0, dwgName: '', dwgPath: '', error: e.message };
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
export {
|
|
1030
|
+
getEntityByHandle,
|
|
1031
|
+
getCadInfo,
|
|
1032
|
+
getEntities,
|
|
1033
|
+
getTextContent,
|
|
1034
|
+
combineText,
|
|
1035
|
+
getDwgName,
|
|
1036
|
+
getDwgPath,
|
|
1037
|
+
getBlockRefs,
|
|
1038
|
+
getTables,
|
|
1039
|
+
getPackages,
|
|
1040
|
+
getPlatforms,
|
|
1041
|
+
getDwgList,
|
|
1042
|
+
getDraftingStandards,
|
|
1043
|
+
getCodingStandards,
|
|
1044
|
+
getCadEvents,
|
|
1045
|
+
};
|