@atlisp/mcp 1.8.18 → 1.8.20
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/atlisp-mcp.js +3969 -2535
- package/dist/cad-worker.js +53 -19
- package/dist/config.js +41 -2
- package/dist/handlers/batch-transaction-handlers.js +70 -0
- package/dist/handlers/cad-handlers.js +66 -91
- package/dist/handlers/draw-handlers.js +118 -165
- package/dist/handlers/entity-handlers.js +61 -111
- package/dist/handlers/layer-handlers.js +70 -98
- package/dist/handlers/lint-handlers.js +64 -0
- package/dist/handlers/resource-defs.js +2 -0
- package/dist/handlers/resource-handlers.js +1288 -26
- package/dist/lisp-security.js +0 -17
- package/dist/pipelines/analyze-and-report.json +16 -0
- package/dist/pipelines/batch-export-pdfs.json +11 -0
- package/dist/pipelines/batch-layer-move.json +19 -0
- package/dist/pipelines/export-current-drawing.json +18 -0
- package/dist/pipelines/purge-and-save.json +16 -0
- package/dist/prompts/definitions/analyze-drawing.json +6 -0
- package/dist/prompts/definitions/batch-draw-lines.json +14 -0
- package/dist/prompts/definitions/coding-conventions.json +11 -0
- package/dist/prompts/definitions/color-convert.json +30 -0
- package/dist/prompts/definitions/curve-analysis.json +26 -0
- package/dist/prompts/definitions/excel-report.json +9 -0
- package/dist/prompts/definitions/export-entities.json +17 -0
- package/dist/prompts/definitions/generate-dimension.json +8 -0
- package/dist/prompts/definitions/geometry-calc.json +9 -0
- package/dist/prompts/definitions/json-exchange.json +22 -0
- package/dist/prompts/definitions/manage-blocks.json +28 -0
- package/dist/prompts/definitions/manage-layers.json +35 -0
- package/dist/prompts/definitions/pickset-filter.json +31 -0
- package/dist/prompts/definitions/string-process.json +40 -0
- package/dist/prompts/definitions/test-autolisp.json +33 -0
- package/dist/prompts/definitions/text-process.json +26 -0
- package/dist/prompts/definitions/workflow-3d-model.json +24 -0
- package/dist/prompts/definitions/workflow-batch-operations.json +24 -0
- package/dist/prompts/definitions/workflow-block-manage.json +32 -0
- package/dist/prompts/definitions/workflow-drawing-export.json +23 -0
- package/dist/prompts/definitions/workflow-entity-analysis.json +23 -0
- package/dist/prompts/definitions/workflow-layer-management.json +38 -0
- package/dist/prompts/definitions/workflow-plot-publish.json +28 -0
- package/dist/prompts/definitions/workflow-sheetset.json +29 -0
- package/dist/prompts/definitions/workflow-text-process.json +29 -0
- package/dist/prompts/definitions/workflow-xref-manage.json +28 -0
- package/package.json +7 -3
|
@@ -1,22 +1,1225 @@
|
|
|
1
|
-
import { RESOURCES } from './resource-defs.js';
|
|
2
|
-
import { clearCache } from './resource-cache.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import { RESOURCES, TBL_QUERIES, TBL_KEY_ALIAS, STANDARDS_DIR } from './resource-defs.js';
|
|
2
|
+
import { clearCache, getCached, setCache } from './resource-cache.js';
|
|
3
|
+
import { cad } from '../cad.js';
|
|
4
|
+
import { CAD_PLATFORMS, FILE_EXTENSIONS } from '../constants.js';
|
|
5
|
+
import { log } from '../logger.js';
|
|
6
|
+
import { parseLispList, parseLispRecursive, lispPairsToObject, escapeLispString } from '../handler-utils.js';
|
|
7
|
+
import { createError, ERROR_CODES } from '../errors.js';
|
|
8
|
+
import config from '../config.js';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// LISP Helper Code (sent once per session via cad-worker.js cache)
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
const ENTITY_PROPERTIES_LISP = `
|
|
17
|
+
(defun entity:to-list (val)
|
|
18
|
+
(cond
|
|
19
|
+
((= (type val) 'VARIANT) (entity:to-list (vlax-variant-value val)))
|
|
20
|
+
((= (type val) 'SAFEARRAY) (vlax-safearray->list val))
|
|
21
|
+
(T val)))
|
|
22
|
+
|
|
23
|
+
(defun entity:try-get (obj names / result val)
|
|
24
|
+
(foreach name names
|
|
25
|
+
(if (not (vl-catch-all-error-p
|
|
26
|
+
(setq val (vl-catch-all-apply
|
|
27
|
+
(function vlax-get-property)
|
|
28
|
+
(list obj name)))))
|
|
29
|
+
(setq result (cons (cons name (entity:to-list val)) result))))
|
|
30
|
+
(reverse result))
|
|
31
|
+
|
|
32
|
+
(setq *common-props* '(ObjectName Handle Layer Color Linetype LinetypeScale Lineweight Visibility Material))
|
|
33
|
+
|
|
34
|
+
(setq *entity-props*
|
|
35
|
+
'((AcDbLine StartPoint EndPoint Angle Delta Length Thickness Normal)
|
|
36
|
+
(AcDbCircle Center Radius Diameter Circumference Area Thickness Normal)
|
|
37
|
+
(AcDbArc Center Radius StartAngle EndAngle TotalAngle ArcLength Area Normal)
|
|
38
|
+
(AcDbPoint Position Thickness Normal)
|
|
39
|
+
(AcDbText TextString Position Height Width ObliqueAngle Rotation StyleName HorizontalAlignment VerticalAlignment Normal Backward UpsideDown)
|
|
40
|
+
(AcDbMText TextString Location Height Width Rotation StyleName AttachmentPoint DrawingDirection LinespacingFactor LinespacingDistance LinespacingStyle FlowDirection)
|
|
41
|
+
(AcDbBlockReference Name InsertionPoint Rotation XScale YScale ZScale Normal)
|
|
42
|
+
(AcDbPolyline Coordinates Closed ConstantWidth Elevation Normal Thickness LinetypeGeneration Area Length)
|
|
43
|
+
(AcDb2dPolyline Coordinates Closed Elevation Normal Thickness LinetypeGeneration Area Length Type Width)
|
|
44
|
+
(AcDb3dPolyline Coordinates Closed Type Normal Length Area)
|
|
45
|
+
(AcDbAlignedDimension DimLinePoint1 DimLinePoint2 ExtLinePoint1 ExtLinePoint2 TextPosition TextRotation Measurement)
|
|
46
|
+
(AcDbRotatedDimension DimLinePoint1 DimLinePoint2 ExtLinePoint1 ExtLinePoint2 TextPosition TextRotation Measurement Rotation)
|
|
47
|
+
(AcDbRadialDimension Center Diameter LeaderLength TextPosition)
|
|
48
|
+
(AcDbDiametricDimension Center Diameter LeaderLength TextPosition)
|
|
49
|
+
(AcDbAngularDimension AngleVertex AngleEndPoint ArcPoint TextPosition Measurement)
|
|
50
|
+
(AcDbHatch PatternName PatternType Area Elevation Normal NumberOfLoops AssociativeHatch HatchObjectType PatternAngle PatternScale PatternSpace ISOPenWidth)
|
|
51
|
+
(AcDbSpline Degree NumberOfControlPoints NumberOfFitPoints ControlPoints FitPoints Knots Weights Area Length Closed Periodic StartTanVector EndTanVector Elevation Normal)
|
|
52
|
+
(AcDbEllipse Center MajorRadius MinorRadius RadiusRatio StartAngle EndAngle Area Circumference Normal)
|
|
53
|
+
(AcDbRegion Area Perimeter Centroid MomentsOfInertia PrincipalMoments PrincipalDirections Normal)
|
|
54
|
+
(AcDb3dSolid Volume Area Centroid MomentsOfInertia PrincipalMoments PrincipalDirections)
|
|
55
|
+
(AcDbRasterImage ImageFile ImageWidth ImageHeight Origin Rotation Width Height ShowImage ClippingEnabled)
|
|
56
|
+
(AcDbLeader Coordinates ArrowheadType ArrowheadSize Color ScaleFactor StyleName TextString TextPosition Type Normal)
|
|
57
|
+
(AcDbViewport Center Width Height ScaleFactor CustomScale StandardScale GridOn SnapOn On DisplayLocked)
|
|
58
|
+
(AcDbTable TableStyleName Position Rows Columns Direction FlowDirection)))
|
|
59
|
+
|
|
60
|
+
(defun entity:properties (ent / obj object-name result)
|
|
61
|
+
(setq obj (if (= (type ent) 'VLA-OBJECT) ent (vlax-ename->vla-object ent)))
|
|
62
|
+
(setq result (entity:try-get obj *common-props*))
|
|
63
|
+
(setq object-name (cdr (assoc 'ObjectName result)))
|
|
64
|
+
(if object-name
|
|
65
|
+
(setq result (append result (entity:try-get obj (cdr (assoc (read object-name) *entity-props*))))))
|
|
66
|
+
result)
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const VLA_HELPER_LISP = `(defun @:get-props (obj prop-names / result p p-sym val)
|
|
70
|
+
(setq result nil)
|
|
71
|
+
(foreach p prop-names
|
|
72
|
+
(setq p-sym (read p))
|
|
73
|
+
(if (vlax-property-available-p obj p-sym)
|
|
74
|
+
(progn
|
|
75
|
+
(setq val (vl-catch-all-apply 'vlax-get (list obj p-sym)))
|
|
76
|
+
(if (not (vl-catch-all-error-p val))
|
|
77
|
+
(progn
|
|
78
|
+
(cond
|
|
79
|
+
((= (type val) 'VLA-OBJECT) (setq val (strcat "@" (vla-get-objectname val))))
|
|
80
|
+
((or (= (type val) 'STR) (= (type val) 'SAFEARRAY) (listp val)) (setq val (vl-prin1-to-string val))))
|
|
81
|
+
(setq result (cons (cons p val) result)))))))
|
|
82
|
+
(reverse result))`;
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// Utility Functions
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
function parseEntity(arr) {
|
|
89
|
+
if (!Array.isArray(arr) || arr.length < 5) return null;
|
|
90
|
+
const [type, handle, layer, color, linetype, ...rest] = arr;
|
|
91
|
+
const entity = {
|
|
92
|
+
type: String(type).toUpperCase().replace(/^"|"$/g, ''),
|
|
93
|
+
handle: String(handle),
|
|
94
|
+
layer: String(layer),
|
|
95
|
+
color,
|
|
96
|
+
linetype: String(linetype)
|
|
97
|
+
};
|
|
98
|
+
const entType = typeof entity.type === 'string' ? entity.type.toUpperCase() : entity.type;
|
|
99
|
+
if (/^(TEXT|MTEXT|ATTRIB|TCH_TEXT)$/.test(entType) && rest[0] != null) {
|
|
100
|
+
entity.text = String(rest[0]);
|
|
101
|
+
}
|
|
102
|
+
if (/^(LINE|LWPOLYLINE|POLYLINE|ARC|CIRCLE|SPLINE|ELLIPSE|XLINE|RAY)$/.test(entType)
|
|
103
|
+
&& Array.isArray(rest[0])) {
|
|
104
|
+
entity.points = rest[0].map(pt => {
|
|
105
|
+
if (Array.isArray(pt) && pt.length >= 2)
|
|
106
|
+
return { x: Number(pt[0]), y: Number(pt[1]), z: Number(pt[2] || 0) };
|
|
107
|
+
return null;
|
|
108
|
+
}).filter(Boolean);
|
|
109
|
+
}
|
|
110
|
+
if (entType === 'INSERT' && rest[0] != null) {
|
|
111
|
+
entity.blockName = String(rest[0]);
|
|
112
|
+
}
|
|
113
|
+
return entity;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parseVlaEntity(obj) {
|
|
117
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
118
|
+
return {
|
|
119
|
+
type: obj.type || null,
|
|
120
|
+
handle: obj.handle || null,
|
|
121
|
+
layer: obj.layer || '0',
|
|
122
|
+
color: obj.color !== undefined ? obj.color : 256,
|
|
123
|
+
linetype: obj.linetype || 'ByLayer'
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function propertyPairsToObject(pairs) {
|
|
128
|
+
if (!Array.isArray(pairs)) return null;
|
|
129
|
+
const obj = {};
|
|
130
|
+
for (const pair of pairs) {
|
|
131
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
132
|
+
obj[String(pair[0])] = pair[1] !== null && pair[1] !== undefined ? pair[1] : null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return obj;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function ensureCad() {
|
|
139
|
+
if (!cad.connected) await cad.connect();
|
|
140
|
+
if (!cad.connected) throw createError(ERROR_CODES.CAD_NOT_CONNECTED);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// LISP Code Builders
|
|
145
|
+
// =============================================================================
|
|
146
|
+
|
|
147
|
+
function buildEntityPropertiesCode({ type, layer, bbox }) {
|
|
148
|
+
const items = [];
|
|
149
|
+
if (type) items.push(`(cons 0 "${escapeLispString(type)}")`);
|
|
150
|
+
if (layer) items.push(`(cons 8 "${escapeLispString(layer)}")`);
|
|
151
|
+
if (bbox) {
|
|
152
|
+
const parts = bbox.split(',').map(s => parseFloat(s.trim()));
|
|
153
|
+
if (parts.length === 4) {
|
|
154
|
+
const [x1, y1, x2, y2] = parts;
|
|
155
|
+
items.push(
|
|
156
|
+
`'(-4 . "<AND")`,
|
|
157
|
+
`'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
|
|
158
|
+
`'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
|
|
159
|
+
`'(-4 . "AND>")`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const filterExpr = items.length ? `(list ${items.join(' ')})` : 'nil';
|
|
164
|
+
const ssgetExpr = items.length ? `(ssget "_X" ${filterExpr})` : '(ssget "_X")';
|
|
165
|
+
|
|
166
|
+
return `(progn
|
|
167
|
+
(vl-load-com)
|
|
168
|
+
${ENTITY_PROPERTIES_LISP}
|
|
169
|
+
(setq @e:total (if (setq @e:ss ${ssgetExpr}) (sslength @e:ss) 0)
|
|
170
|
+
@e:ents nil @e:i 0)
|
|
171
|
+
(while (and @e:ss (< @e:i @e:total) (< (length @e:ents) 3000))
|
|
172
|
+
(setq @e:ents (cons (entity:properties (ssname @e:ss @e:i)) @e:ents)
|
|
173
|
+
@e:i (1+ @e:i)))
|
|
174
|
+
(vl-prin1-to-string (list @e:total (reverse @e:ents))))`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function buildTextContentCode({ layer, bbox, offset, limit }) {
|
|
178
|
+
const items = [
|
|
179
|
+
`'(-4 . "<OR")`,
|
|
180
|
+
`'(0 . "TEXT")`,
|
|
181
|
+
`'(0 . "MTEXT")`,
|
|
182
|
+
`'(0 . "ATTRIB")`,
|
|
183
|
+
`'(-4 . "OR>")`,
|
|
184
|
+
];
|
|
185
|
+
if (layer) items.push(`(cons 8 "${escapeLispString(layer)}")`);
|
|
186
|
+
if (bbox) {
|
|
187
|
+
const parts = bbox.split(',').map(s => parseFloat(s.trim()));
|
|
188
|
+
if (parts.length === 4) {
|
|
189
|
+
const [x1, y1, x2, y2] = parts;
|
|
190
|
+
items.push(
|
|
191
|
+
`'(-4 . "<AND")`,
|
|
192
|
+
`'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
|
|
193
|
+
`'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
|
|
194
|
+
`'(-4 . "AND>")`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const filterExpr = `(list ${items.join(' ')})`;
|
|
199
|
+
|
|
200
|
+
return `(progn
|
|
201
|
+
(vl-load-com)
|
|
202
|
+
(setq ss (ssget "_X" ${filterExpr}))
|
|
203
|
+
(if (null ss)
|
|
204
|
+
(list "total" 0 "offset" ${offset} "limit" ${limit} "texts" nil)
|
|
205
|
+
(progn
|
|
206
|
+
(setq total (sslength ss) i ${offset} texts nil)
|
|
207
|
+
(while (and (< i total) (< (length texts) ${limit}))
|
|
208
|
+
(setq ent (ssname ss i) ed (entget ent) ins (cdr (assoc 10 ed)))
|
|
209
|
+
(setq typ (cdr (assoc 0 ed)) h (cdr (assoc 40 ed)))
|
|
210
|
+
(if (null h) (setq h 2.5))
|
|
211
|
+
(setq texts (append texts (list (list
|
|
212
|
+
"text" (vl-prin1-to-string (cdr (assoc 1 ed)))
|
|
213
|
+
"position" (list (car ins) (cadr ins) (caddr ins))
|
|
214
|
+
"height" h
|
|
215
|
+
"handle" (cdr (assoc 5 ed))
|
|
216
|
+
))) i (1+ i)))
|
|
217
|
+
(list "total" total "offset" ${offset} "limit" ${limit} "texts" texts))))
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function buildBlockRefsCode(filterBlockName) {
|
|
222
|
+
const filterItems = ['(cons 0 "INSERT")'];
|
|
223
|
+
if (filterBlockName) {
|
|
224
|
+
filterItems.push(`(cons 2 "${escapeLispString(filterBlockName)}")`);
|
|
225
|
+
}
|
|
226
|
+
const filterCode = `(list ${filterItems.join(' ')})`;
|
|
227
|
+
return `(progn
|
|
228
|
+
(vl-load-com)
|
|
229
|
+
|
|
230
|
+
(defun @br:try-fn (fn arg / r)
|
|
231
|
+
(setq r (vl-catch-all-apply (function (lambda () (apply fn (list arg))))))
|
|
232
|
+
(if (vl-catch-all-error-p r) nil r))
|
|
233
|
+
|
|
234
|
+
(defun @br:props (ent / ed r p2 p10 p50 p41 p42 p43)
|
|
235
|
+
(setq ed (entget ent)
|
|
236
|
+
p10 (cdr (assoc 10 ed))
|
|
237
|
+
p50 (cdr (assoc 50 ed))
|
|
238
|
+
p41 (cdr (assoc 41 ed))
|
|
239
|
+
p42 (cdr (assoc 42 ed))
|
|
240
|
+
p43 (cdr (assoc 43 ed)))
|
|
241
|
+
(setq r (vl-catch-all-apply
|
|
242
|
+
(function (lambda ()
|
|
243
|
+
(list
|
|
244
|
+
(cons "handle" (cdr (assoc 5 ed)))
|
|
245
|
+
(cons "layer" (cdr (assoc 8 ed)))
|
|
246
|
+
(cons "blockName" (cdr (assoc 2 ed)))
|
|
247
|
+
(cons "effectiveBlockName" (cdr (assoc 2 ed)))
|
|
248
|
+
(cons "insertionPoint" (list (car p10) (cadr p10) (caddr p10)))
|
|
249
|
+
(cons "rotation" p50)
|
|
250
|
+
(cons "scale" (list p41 p42 p43))
|
|
251
|
+
(cons "attributes" (@br:try-fn 'block:get-attributes ent))
|
|
252
|
+
(cons "dynamicProperties" (@br:try-fn 'block:get-properties ent)))))))
|
|
253
|
+
(if (vl-catch-all-error-p r) (list (cons "handle" (cdr (assoc 5 ed)))) r))
|
|
254
|
+
|
|
255
|
+
(setq ss (ssget "_X" ${filterCode}))
|
|
256
|
+
(if (null ss)
|
|
257
|
+
(list (cons "total" 0) (cons "blockRefs" nil))
|
|
258
|
+
(progn
|
|
259
|
+
(setq total (sslength ss) i 0 refs nil)
|
|
260
|
+
(while (< i total)
|
|
261
|
+
(setq r (@br:props (ssname ss i)))
|
|
262
|
+
(if r (setq refs (cons r refs)))
|
|
263
|
+
(setq i (1+ i)))
|
|
264
|
+
(list (cons "total" total) (cons "blockRefs" (list (reverse refs)))))))`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function buildTablesCode(tblName) {
|
|
268
|
+
const raw = tblName.toLowerCase();
|
|
269
|
+
const key = TBL_KEY_ALIAS[raw] || raw;
|
|
270
|
+
const query = TBL_QUERIES[key];
|
|
271
|
+
const isVlaTable = key !== 'layer';
|
|
272
|
+
if (query) {
|
|
273
|
+
if (isVlaTable) {
|
|
274
|
+
return `(progn (vl-load-com) ${VLA_HELPER_LISP} ${query.lisp})`;
|
|
275
|
+
}
|
|
276
|
+
return `(progn (vl-load-com) ${query.lisp})`;
|
|
277
|
+
}
|
|
278
|
+
const parts = Object.values(TBL_QUERIES).map(q => `(vl-catch-all-apply '(lambda () ${q.lisp}))`);
|
|
279
|
+
return `(progn (vl-load-com) ${VLA_HELPER_LISP} (list ${parts.join(' ')}))`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function parseTableRecord(rec, tableName) {
|
|
283
|
+
if (!rec || !Array.isArray(rec)) return null;
|
|
284
|
+
const obj = {};
|
|
285
|
+
const isLayer = tableName === 'layer';
|
|
286
|
+
for (const pair of rec) {
|
|
287
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
288
|
+
const key = String(pair[0]);
|
|
289
|
+
const val = pair[1];
|
|
290
|
+
const isDxfCode = typeof pair[0] === 'number';
|
|
291
|
+
if (isDxfCode) {
|
|
292
|
+
if (key === '2') obj.name = String(val);
|
|
293
|
+
else if (key === '62') obj.color = typeof val === 'number' ? val : 7;
|
|
294
|
+
else if (key === '6') obj.linetype = String(val);
|
|
295
|
+
else if (key === '70') {
|
|
296
|
+
obj.flags = typeof val === 'number' ? val : 0;
|
|
297
|
+
if (isLayer) {
|
|
298
|
+
obj.frozen = (val & 1) !== 0;
|
|
299
|
+
obj.locked = (val & 4) !== 0;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else if (key === '60') obj.on = val === 0;
|
|
303
|
+
else if (key === '1') obj.font = String(val);
|
|
304
|
+
else if (key === '40') obj.height = typeof val === 'number' ? val : 0;
|
|
305
|
+
else if (key === '41') obj.width = typeof val === 'number' ? val : 1;
|
|
306
|
+
else if (key === '42') obj.oblique = typeof val === 'number' ? val : 0;
|
|
307
|
+
else if (key === '3') obj.bigfont = String(val);
|
|
308
|
+
else if (key === '50') obj.flags = typeof val === 'number' ? val : 0;
|
|
309
|
+
} else {
|
|
310
|
+
if (key === 'Name') obj.name = val;
|
|
311
|
+
obj[key] = val;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return obj.name ? obj : null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// =============================================================================
|
|
319
|
+
// Resource Reader Functions
|
|
320
|
+
// =============================================================================
|
|
321
|
+
|
|
322
|
+
async function getEntityByHandle(handle) {
|
|
323
|
+
await ensureCad();
|
|
324
|
+
const code = `(progn
|
|
325
|
+
(vl-load-com)
|
|
326
|
+
(if (setq ent (handent "${escapeLispString(handle)}"))
|
|
327
|
+
(progn
|
|
328
|
+
(setq ed (entget ent))
|
|
329
|
+
(list
|
|
330
|
+
"type" (cdr (assoc 0 ed))
|
|
331
|
+
"handle" (cdr (assoc 5 ed))
|
|
332
|
+
"layer" (cdr (assoc 8 ed))
|
|
333
|
+
"color" (if (assoc 62 ed) (cdr (assoc 62 ed)) 256)
|
|
334
|
+
"linetype" (if (assoc 6 ed) (cdr (assoc 6 ed)) "ByLayer")
|
|
335
|
+
"elevation" (if (assoc 38 ed) (cdr (assoc 38 ed)) 0.0)
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
nil
|
|
339
|
+
)
|
|
340
|
+
)`;
|
|
341
|
+
try {
|
|
342
|
+
const result = await cad.sendCommandWithResult(code);
|
|
343
|
+
if (!result || result === 'nil') return { error: `未找到实体: ${handle}` };
|
|
344
|
+
try {
|
|
345
|
+
return JSON.parse(result);
|
|
346
|
+
} catch {
|
|
347
|
+
return { raw: result };
|
|
348
|
+
}
|
|
349
|
+
} catch (e) {
|
|
350
|
+
return { error: e.message };
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function getCadInfo() {
|
|
355
|
+
const cached = getCached('cad:info');
|
|
356
|
+
if (cached) return cached;
|
|
357
|
+
|
|
358
|
+
if (!cad.connected) {
|
|
359
|
+
await cad.connect();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (!cad.connected) {
|
|
363
|
+
return { connected: false, platform: null, version: null, busy: false };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let busy = false;
|
|
367
|
+
try {
|
|
368
|
+
busy = await cad.isBusy();
|
|
369
|
+
} catch (e) {
|
|
370
|
+
log(`Error checking CAD busy status: ${e.message}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const result = {
|
|
374
|
+
connected: true,
|
|
375
|
+
platform: cad.getPlatform(),
|
|
376
|
+
version: cad.getVersion(),
|
|
377
|
+
busy
|
|
378
|
+
};
|
|
379
|
+
setCache('cad:info', result);
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function getEntitiesSummary(cacheKey) {
|
|
384
|
+
const summaryCode = `(progn
|
|
385
|
+
(vl-load-com)
|
|
386
|
+
(setq ss (ssget "_X"))
|
|
387
|
+
(if ss
|
|
388
|
+
(progn
|
|
389
|
+
(setq total (sslength ss) i 0 typeCounts (list))
|
|
390
|
+
(while (< i total)
|
|
391
|
+
(setq ent (ssname ss i) ed (entget ent) typ (cdr (assoc 0 ed)))
|
|
392
|
+
(setq typeCounts (cons typ typeCounts) i (1+ i)))
|
|
393
|
+
(setq counts nil)
|
|
394
|
+
(foreach item typeCounts
|
|
395
|
+
(if (assoc item counts)
|
|
396
|
+
(setq counts (subst (cons item (1+ (cdr (assoc item counts)))) (assoc item counts) counts))
|
|
397
|
+
(setq counts (cons (cons item 1) counts))))
|
|
398
|
+
(cons (list "total" total) (reverse counts))
|
|
399
|
+
)
|
|
400
|
+
(list (list "total" 0))
|
|
401
|
+
)
|
|
402
|
+
)`;
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const resultStr = await cad.sendCommandWithResult(summaryCode);
|
|
406
|
+
|
|
407
|
+
if (!resultStr || resultStr === '' || resultStr === 'nil') {
|
|
408
|
+
return { total: 0, summary: [] };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const parsed = parseLispRecursive(resultStr);
|
|
412
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
413
|
+
return { total: 0, summary: [] };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let total = 0;
|
|
417
|
+
const summary = [];
|
|
418
|
+
|
|
419
|
+
for (const item of parsed) {
|
|
420
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
421
|
+
const key = String(item[0]);
|
|
422
|
+
const val = item[1];
|
|
423
|
+
if (key === 'total') {
|
|
424
|
+
total = typeof val === 'number' ? val : 0;
|
|
425
|
+
} else {
|
|
426
|
+
summary.push({ type: key, count: typeof val === 'number' ? val : 0 });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
summary.sort((a, b) => b.count - a.count);
|
|
432
|
+
|
|
433
|
+
const result = { total, summary };
|
|
434
|
+
log(`getEntitiesSummary: total=${total}, types=${summary.length}`);
|
|
435
|
+
setCache(cacheKey, result);
|
|
436
|
+
return result;
|
|
437
|
+
} catch (e) {
|
|
438
|
+
log(`getEntitiesSummary error: ${e.message}`);
|
|
439
|
+
return { total: 0, summary: [] };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function getEntities(params = {}) {
|
|
444
|
+
const cacheKey = `dwg:entities:${JSON.stringify(params)}`;
|
|
445
|
+
const cached = getCached(cacheKey);
|
|
446
|
+
if (cached) return cached;
|
|
447
|
+
|
|
448
|
+
await ensureCad();
|
|
449
|
+
|
|
450
|
+
const hasFilter = params.type || params.layer || params.bbox;
|
|
451
|
+
|
|
452
|
+
if (!hasFilter) {
|
|
453
|
+
return await getEntitiesSummary(cacheKey);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
const code = buildEntityPropertiesCode({
|
|
458
|
+
type: params.type || null,
|
|
459
|
+
layer: params.layer || null,
|
|
460
|
+
bbox: params.bbox || null,
|
|
461
|
+
});
|
|
462
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
463
|
+
|
|
464
|
+
if (!resultStr || resultStr === '' || resultStr === 'nil') {
|
|
465
|
+
return { total: 0, entities: [] };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let total = 0;
|
|
469
|
+
let entities = [];
|
|
470
|
+
|
|
471
|
+
const parsed = parseLispRecursive(resultStr);
|
|
472
|
+
if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
|
|
473
|
+
total = typeof parsed[0] === 'number' ? parsed[0] : 0;
|
|
474
|
+
const rawEntities = Array.isArray(parsed[1]) ? parsed[1] : [];
|
|
475
|
+
entities = rawEntities.map(propertyPairsToObject).filter(Boolean);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const result = { total, entities };
|
|
479
|
+
|
|
480
|
+
log(`getEntities: total=${total}, returned=${entities.length}`);
|
|
481
|
+
setCache(cacheKey, result);
|
|
482
|
+
return result;
|
|
483
|
+
} catch (e) {
|
|
484
|
+
log(`getEntities error: ${e.message}`);
|
|
485
|
+
return { total: 0, entities: [] };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function getTextContent(params = {}) {
|
|
490
|
+
const cacheKey = `dwg:text-content:${JSON.stringify(params)}`;
|
|
491
|
+
const cached = getCached(cacheKey);
|
|
492
|
+
if (cached) return cached;
|
|
493
|
+
|
|
494
|
+
await ensureCad();
|
|
495
|
+
|
|
496
|
+
const offset = Math.max(0, parseInt(params.offset) || 0);
|
|
497
|
+
const limit = Math.min(Math.max(1, parseInt(params.limit) || 5000), 5000);
|
|
498
|
+
|
|
499
|
+
try {
|
|
500
|
+
const code = buildTextContentCode({ layer: params.layer, bbox: params.bbox, offset, limit });
|
|
501
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
502
|
+
|
|
503
|
+
log(`[DEBUG text-content] resultStr length=${resultStr?.length} first300=${resultStr?.substring(0, 300)}`);
|
|
504
|
+
|
|
505
|
+
const parsed = parseLispRecursive(resultStr);
|
|
506
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
507
|
+
throw new Error(`text-content: 解析 Lisp 结果失败: raw="${String(resultStr).substring(0, 500)}"`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const data = {};
|
|
511
|
+
let rawTexts = [];
|
|
512
|
+
for (let i = 0; i < parsed.length - 1; i += 2) {
|
|
513
|
+
const key = parsed[i];
|
|
514
|
+
const val = parsed[i + 1];
|
|
515
|
+
if (typeof key === 'string' || typeof key === 'number') {
|
|
516
|
+
if (key === 'texts' && Array.isArray(val)) {
|
|
517
|
+
rawTexts = val;
|
|
518
|
+
} else {
|
|
519
|
+
data[String(key)] = val;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
data.texts = rawTexts.map(item => {
|
|
524
|
+
if (!Array.isArray(item)) return null;
|
|
525
|
+
const obj = {};
|
|
526
|
+
for (let i = 0; i < item.length - 1; i += 2) {
|
|
527
|
+
const key = item[i];
|
|
528
|
+
const val = item[i + 1];
|
|
529
|
+
if (key === 'text') obj.text = String(val || '');
|
|
530
|
+
else if (key === 'position') {
|
|
531
|
+
obj.position = Array.isArray(val) ? val.map(v => typeof v === 'number' ? v : 0) : [];
|
|
532
|
+
}
|
|
533
|
+
else if (key === 'height') obj.height = typeof val === 'number' ? val : 2.5;
|
|
534
|
+
else if (key === 'handle') obj.handle = String(val || '');
|
|
535
|
+
}
|
|
536
|
+
return obj.text ? obj : null;
|
|
537
|
+
}).filter(Boolean);
|
|
538
|
+
|
|
539
|
+
setCache(cacheKey, data);
|
|
540
|
+
return data;
|
|
541
|
+
} catch (e) {
|
|
542
|
+
log(`getTextContent error: ${e.message}`);
|
|
543
|
+
throw e;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function medianHeight(texts) {
|
|
548
|
+
const heights = texts.filter(t => t.height > 0).map(t => t.height);
|
|
549
|
+
if (heights.length === 0) return 2.5;
|
|
550
|
+
heights.sort((a, b) => a - b);
|
|
551
|
+
const mid = Math.floor(heights.length / 2);
|
|
552
|
+
return heights.length % 2 !== 0 ? heights[mid] : (heights[mid - 1] + heights[mid]) / 2;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function shouldContinueLine(prevText, nextText) {
|
|
556
|
+
if (!prevText || !nextText) return false;
|
|
557
|
+
const trimmed = prevText.trimEnd();
|
|
558
|
+
if (trimmed.length === 0) return false;
|
|
559
|
+
const lastChar = trimmed[trimmed.length - 1];
|
|
560
|
+
const sentenceEnd = '.!?。!?';
|
|
561
|
+
if (sentenceEnd.includes(lastChar)) return false;
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function combineText(params = {}) {
|
|
566
|
+
const content = await getTextContent(params);
|
|
567
|
+
const texts = content.texts || [];
|
|
568
|
+
if (texts.length === 0) return { total: 0, blocks: [], medianHeight: 2.5 };
|
|
569
|
+
|
|
570
|
+
const mh = medianHeight(texts);
|
|
571
|
+
const yTolerance = params.yTolerance || mh * 1.2;
|
|
572
|
+
const xGapRatio = mh * 3.0;
|
|
573
|
+
const blockSpacing = mh * 2.5;
|
|
574
|
+
|
|
575
|
+
const rows = [];
|
|
576
|
+
for (const item of texts) {
|
|
577
|
+
if (!item.text) continue;
|
|
578
|
+
const [x, y] = item.position || [0, 0];
|
|
579
|
+
let matched = null;
|
|
580
|
+
for (const row of rows) {
|
|
581
|
+
if (Math.abs(row.y - y) <= yTolerance) { matched = row; break; }
|
|
582
|
+
}
|
|
583
|
+
if (matched) {
|
|
584
|
+
matched.items.push({ x, text: item.text, height: item.height || mh });
|
|
585
|
+
} else {
|
|
586
|
+
rows.push({ y, items: [{ x, text: item.text, height: item.height || mh }] });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
for (const row of rows) {
|
|
591
|
+
row.items.sort((a, b) => a.x - b.x);
|
|
592
|
+
row.lines = [];
|
|
593
|
+
let cur = [];
|
|
594
|
+
for (const item of row.items) {
|
|
595
|
+
if (cur.length === 0) { cur.push(item); continue; }
|
|
596
|
+
const prev = cur[cur.length - 1];
|
|
597
|
+
const localH = prev.height || mh;
|
|
598
|
+
if (item.x - prev.x > xGapRatio) {
|
|
599
|
+
row.lines.push(cur.map(t => t.text).join(''));
|
|
600
|
+
cur = [item];
|
|
601
|
+
} else {
|
|
602
|
+
cur.push(item);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (cur.length > 0) row.lines.push(cur.map(t => t.text).join(''));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
rows.sort((a, b) => a.y - b.y);
|
|
609
|
+
const rawBlocks = [];
|
|
610
|
+
let curBlock = null;
|
|
611
|
+
for (const row of rows) {
|
|
612
|
+
if (!curBlock) {
|
|
613
|
+
curBlock = { rows: [row] };
|
|
614
|
+
} else {
|
|
615
|
+
const last = curBlock.rows[curBlock.rows.length - 1];
|
|
616
|
+
if (row.y - last.y > blockSpacing) {
|
|
617
|
+
rawBlocks.push(curBlock);
|
|
618
|
+
curBlock = { rows: [row] };
|
|
619
|
+
} else {
|
|
620
|
+
curBlock.rows.push(row);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (curBlock) rawBlocks.push(curBlock);
|
|
625
|
+
|
|
626
|
+
const blocks = rawBlocks.map(block => {
|
|
627
|
+
const orderedRows = [...block.rows].reverse();
|
|
628
|
+
const lines = [];
|
|
629
|
+
for (let i = 0; i < orderedRows.length; i++) {
|
|
630
|
+
const rowText = orderedRows[i].lines.join(' ');
|
|
631
|
+
if (i === 0) {
|
|
632
|
+
lines.push(rowText);
|
|
633
|
+
} else {
|
|
634
|
+
const prevLine = lines[lines.length - 1];
|
|
635
|
+
if (shouldContinueLine(prevLine, rowText)) {
|
|
636
|
+
lines[lines.length - 1] = prevLine + rowText;
|
|
637
|
+
} else {
|
|
638
|
+
lines.push(rowText);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
text: lines.join('\n'),
|
|
644
|
+
y: orderedRows[0].y,
|
|
645
|
+
rowCount: orderedRows.length,
|
|
646
|
+
lines
|
|
647
|
+
};
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
blocks.sort((a, b) => b.y - a.y);
|
|
651
|
+
return { total: blocks.length, blocks, medianHeight: mh };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async function getDwgName() {
|
|
655
|
+
await ensureCad();
|
|
656
|
+
try {
|
|
657
|
+
const code = `(vl-princ-to-string (getvar "dwgname"))`;
|
|
658
|
+
const name = await cad.sendCommandWithResult(code);
|
|
659
|
+
return { name: name || null };
|
|
660
|
+
} catch (e) {
|
|
661
|
+
return { name: null };
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async function getDwgPath() {
|
|
666
|
+
await ensureCad();
|
|
667
|
+
try {
|
|
668
|
+
const code = `(progn
|
|
669
|
+
(setq prefix (vl-princ-to-string (getvar "dwgprefix")))
|
|
670
|
+
(setq name (vl-princ-to-string (getvar "dwgname")))
|
|
671
|
+
(list (strcat prefix name) name)
|
|
672
|
+
)`;
|
|
673
|
+
const result = await cad.sendCommandWithResult(code);
|
|
674
|
+
|
|
675
|
+
if (!result || result === "nil" || result === "") {
|
|
676
|
+
return { path: null, name: null };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
let parsed = null;
|
|
680
|
+
try { parsed = JSON.parse(result); } catch (e) { log(`resource-handlers: JSON parse error: ${e.message}`); }
|
|
681
|
+
if (!parsed) {
|
|
682
|
+
const listResult = parseLispList(result);
|
|
683
|
+
if (listResult && listResult.length >= 2) parsed = listResult;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
|
|
687
|
+
return { path: String(parsed[0]), name: String(parsed[1]) };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return { path: result, name: result };
|
|
691
|
+
} catch (e) {
|
|
692
|
+
return { path: null, name: null };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async function getBlockRefs(params = {}) {
|
|
697
|
+
const cacheKey = `dwg:block-refs:${params.blockName || 'all'}`;
|
|
698
|
+
const cached = getCached(cacheKey);
|
|
699
|
+
if (cached) return cached;
|
|
700
|
+
|
|
701
|
+
await ensureCad();
|
|
702
|
+
|
|
703
|
+
try {
|
|
704
|
+
const code = buildBlockRefsCode(params.blockName);
|
|
705
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
706
|
+
|
|
707
|
+
const parsed = parseLispRecursive(resultStr);
|
|
708
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
709
|
+
throw new Error(`block-refs: 解析 Lisp 结果失败: raw="${String(resultStr).substring(0, 500)}"`);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const result = {};
|
|
713
|
+
for (const pair of parsed) {
|
|
714
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
715
|
+
result[String(pair[0])] = pair[1];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const blockRefs = Array.isArray(result.blockRefs) ? result.blockRefs : [];
|
|
719
|
+
const finalResult = {
|
|
720
|
+
total: typeof result.total === 'number' ? result.total : blockRefs.length,
|
|
721
|
+
blockRefs: blockRefs.map(br => {
|
|
722
|
+
const obj = {};
|
|
723
|
+
if (Array.isArray(br)) {
|
|
724
|
+
br.forEach(pair => {
|
|
725
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
726
|
+
const key = pair[0];
|
|
727
|
+
let val = pair.length === 2 ? pair[1] : pair.slice(1);
|
|
728
|
+
if (key === 'insertionPoint' && Array.isArray(val)) {
|
|
729
|
+
val = val.map(v => typeof v === 'number' ? v : 0);
|
|
730
|
+
} else if (key === 'scale' && Array.isArray(val)) {
|
|
731
|
+
val = val.map(v => typeof v === 'number' ? v : 1);
|
|
732
|
+
} else if (key === 'isDynamic') {
|
|
733
|
+
val = val === true || val === 'true' || val === 1;
|
|
734
|
+
} else if (key === 'attributes' && Array.isArray(val)) {
|
|
735
|
+
const o = {};
|
|
736
|
+
val.forEach(p => { if (Array.isArray(p) && p.length >= 2) o[String(p[0])] = p[1]; });
|
|
737
|
+
val = o;
|
|
738
|
+
} else if (key === 'dynamicProperties' && Array.isArray(val)) {
|
|
739
|
+
const o = {};
|
|
740
|
+
val.forEach(p => { if (Array.isArray(p) && p.length >= 2) o[String(p[0])] = p[1]; });
|
|
741
|
+
val = o;
|
|
742
|
+
}
|
|
743
|
+
obj[key] = val;
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
return obj;
|
|
748
|
+
})
|
|
749
|
+
};
|
|
750
|
+
setCache(cacheKey, finalResult);
|
|
751
|
+
return finalResult;
|
|
752
|
+
} catch (e) {
|
|
753
|
+
log(`getBlockRefs error: ${e.message}`);
|
|
754
|
+
throw e;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
async function getTables(params = {}) {
|
|
759
|
+
const tblName = params.tbl || params.type || 'all';
|
|
760
|
+
const cacheKey = `dwg:tbl:${tblName}`;
|
|
761
|
+
const cached = getCached(cacheKey);
|
|
762
|
+
if (cached) return cached;
|
|
763
|
+
|
|
764
|
+
await ensureCad();
|
|
765
|
+
|
|
766
|
+
try {
|
|
767
|
+
const code = buildTablesCode(tblName);
|
|
768
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
769
|
+
|
|
770
|
+
if (!resultStr || resultStr === '' || resultStr === 'nil') {
|
|
771
|
+
return { table: tblName, data: [] };
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const parsed = parseLispRecursive(resultStr);
|
|
775
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
776
|
+
return { table: tblName, total: 0, data: [] };
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
let result;
|
|
780
|
+
if (parsed.length >= 3 && parsed[0] && String(parsed[0][0]).toUpperCase() === 'TABLE') {
|
|
781
|
+
const tableName = String(parsed[0][1]).toLowerCase();
|
|
782
|
+
let total = 0;
|
|
783
|
+
let tblData = [];
|
|
784
|
+
for (const item of parsed) {
|
|
785
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
786
|
+
const key = String(item[0]).toUpperCase();
|
|
787
|
+
if (key === 'TOTAL') total = typeof item[1] === 'number' ? item[1] : 0;
|
|
788
|
+
else if (key === 'DATA') {
|
|
789
|
+
if (Array.isArray(item[1]) && item[1].length > 0) {
|
|
790
|
+
tblData = item[1];
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (tblData.length === 0 && parsed.length > 2) {
|
|
796
|
+
tblData = parsed.slice(2);
|
|
797
|
+
}
|
|
798
|
+
result = {
|
|
799
|
+
table: tableName,
|
|
800
|
+
total: total,
|
|
801
|
+
data: tblData.map(r => parseTableRecord(r, tableName)).filter(Boolean)
|
|
802
|
+
};
|
|
803
|
+
} else {
|
|
804
|
+
const allData = {};
|
|
805
|
+
parsed.forEach(item => {
|
|
806
|
+
if (!Array.isArray(item) || item.length < 2) return;
|
|
807
|
+
let tableKey = null;
|
|
808
|
+
const entry = { total: 0, data: [] };
|
|
809
|
+
if (Array.isArray(item[0]) && item[0].length >= 2 && String(item[0][0]).toLowerCase() === 'table') {
|
|
810
|
+
tableKey = String(item[0][1]).toLowerCase();
|
|
811
|
+
for (let i = 1; i < item.length; i++) {
|
|
812
|
+
const sub = item[i];
|
|
813
|
+
if (!Array.isArray(sub) || sub.length < 2) continue;
|
|
814
|
+
const subKey = String(sub[0]).toUpperCase();
|
|
815
|
+
if (subKey === 'TOTAL') entry.total = typeof sub[1] === 'number' ? sub[1] : 0;
|
|
816
|
+
else if (subKey === 'DATA') {
|
|
817
|
+
const raw = Array.isArray(sub[1]) ? sub[1] : sub.slice(1);
|
|
818
|
+
entry.data = raw.map(r => parseTableRecord(r, tableKey)).filter(Boolean);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
} else {
|
|
822
|
+
tableKey = String(item[0]).toLowerCase();
|
|
823
|
+
for (let i = 1; i < item.length; i++) {
|
|
824
|
+
const sub = item[i];
|
|
825
|
+
if (!Array.isArray(sub) || sub.length < 2) continue;
|
|
826
|
+
const subKey = String(sub[0]).toUpperCase();
|
|
827
|
+
if (subKey === 'TOTAL') {
|
|
828
|
+
entry.total = typeof sub[1] === 'number' ? sub[1] : 0;
|
|
829
|
+
} else if (subKey === 'DATA') {
|
|
830
|
+
const raw = Array.isArray(sub[1]) ? sub[1] : sub.slice(1);
|
|
831
|
+
entry.data = raw.map(r => parseTableRecord(r, tableKey)).filter(Boolean);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (tableKey) allData[tableKey] = entry;
|
|
836
|
+
});
|
|
837
|
+
result = allData;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
setCache(cacheKey, result);
|
|
841
|
+
return result;
|
|
842
|
+
} catch (e) {
|
|
843
|
+
log(`getTables error: ${e.message}`);
|
|
844
|
+
return { table: tblName, data: [] };
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async function getPackages(filterName = null) {
|
|
849
|
+
const cacheKey = filterName ? `packages:${filterName}` : 'packages:all';
|
|
850
|
+
const cached = getCached(cacheKey);
|
|
851
|
+
if (cached) return cached;
|
|
852
|
+
|
|
853
|
+
await ensureCad();
|
|
854
|
+
|
|
855
|
+
try {
|
|
856
|
+
const code = `(mapcar 'cdr @::*local-pkgs*)`;
|
|
857
|
+
const result = await cad.sendCommandWithResult(code);
|
|
858
|
+
|
|
859
|
+
if (!result || result === '' || result === 'nil') {
|
|
860
|
+
return [];
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
let raw = [];
|
|
864
|
+
try {
|
|
865
|
+
raw = JSON.parse(result);
|
|
866
|
+
} catch {
|
|
867
|
+
raw = parseLispList(result) || [];
|
|
868
|
+
}
|
|
869
|
+
if (!Array.isArray(raw)) raw = [];
|
|
870
|
+
|
|
871
|
+
const packages = raw.map(item => {
|
|
872
|
+
if (Array.isArray(item)) {
|
|
873
|
+
const pkg = { name: '', version: '0.0.0', description: '', loaded: true };
|
|
874
|
+
for (const entry of item) {
|
|
875
|
+
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
876
|
+
const [[key, val]] = Object.entries(entry);
|
|
877
|
+
const k = String(key);
|
|
878
|
+
const v = String(val);
|
|
879
|
+
if (k === ':NAME') pkg.name = v;
|
|
880
|
+
else if (k === ':VERSION') pkg.version = v;
|
|
881
|
+
else if (k === ':DESCRIPTION') pkg.description = v;
|
|
882
|
+
else if (k === ':FULL-NAME') pkg.fullName = v;
|
|
883
|
+
else if (k === ':AUTHOR') pkg.author = v;
|
|
884
|
+
else if (k === ':CATEGORY') pkg.category = v;
|
|
885
|
+
else if (k === ':URL') pkg.url = v;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return pkg;
|
|
889
|
+
}
|
|
890
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
891
|
+
return { name: String(item), version: '0.0.0', loaded: true };
|
|
892
|
+
}
|
|
893
|
+
if (item && typeof item === 'object') {
|
|
894
|
+
const pkg = { name: '', version: '0.0.0', description: '', loaded: true };
|
|
895
|
+
const nameVal = item.name || item.Name || item.NAME || item[':NAME'];
|
|
896
|
+
if (nameVal) pkg.name = String(nameVal);
|
|
897
|
+
const verVal = item.version || item.Version || item.VERSION || item[':VERSION'];
|
|
898
|
+
if (verVal) pkg.version = String(verVal);
|
|
899
|
+
const descVal = item.description || item.Description || item.DESCRIPTION || item[':DESCRIPTION'];
|
|
900
|
+
if (descVal) pkg.description = String(descVal);
|
|
901
|
+
if (item.fullName || item.full_name) pkg.fullName = String(item.fullName || item.full_name);
|
|
902
|
+
if (item.author || item.Author) pkg.author = String(item.author || item.Author);
|
|
903
|
+
if (item.category || item.Category) pkg.category = String(item.category || item.Category);
|
|
904
|
+
if (item.url || item.Url || item.URL) pkg.url = String(item.url || item.Url || item.URL);
|
|
905
|
+
return pkg;
|
|
906
|
+
}
|
|
907
|
+
return null;
|
|
908
|
+
}).filter(Boolean);
|
|
909
|
+
|
|
910
|
+
if (filterName) {
|
|
911
|
+
const filtered = packages.filter(p => p.name === filterName);
|
|
912
|
+
setCache(cacheKey, filtered);
|
|
913
|
+
return filtered;
|
|
914
|
+
}
|
|
915
|
+
setCache(cacheKey, packages);
|
|
916
|
+
return packages;
|
|
917
|
+
} catch (e) {
|
|
918
|
+
return [];
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function getPlatforms() {
|
|
923
|
+
return CAD_PLATFORMS.map(name => ({
|
|
924
|
+
name,
|
|
925
|
+
extensions: FILE_EXTENSIONS[name] || []
|
|
926
|
+
}));
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
async function getDwgList() {
|
|
930
|
+
const cacheKey = 'cad:dwgs';
|
|
931
|
+
const cached = getCached(cacheKey);
|
|
932
|
+
if (cached) return cached;
|
|
933
|
+
|
|
934
|
+
await ensureCad();
|
|
935
|
+
|
|
936
|
+
try {
|
|
937
|
+
const code = `(progn
|
|
938
|
+
(vl-load-com)
|
|
939
|
+
(setq result nil)
|
|
940
|
+
(vlax-for doc (vla-get-Documents (vla-get-Application (vlax-get-acad-object)))
|
|
941
|
+
(setq result (cons (list (vla-get-Name doc) (vla-get-FullName doc)) result))
|
|
942
|
+
)
|
|
943
|
+
(reverse result)
|
|
944
|
+
)`;
|
|
945
|
+
const result = await cad.sendCommandWithResult(code);
|
|
946
|
+
|
|
947
|
+
if (!result || result === "" || result === "nil") {
|
|
948
|
+
return [];
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
let parsed = [];
|
|
952
|
+
try {
|
|
953
|
+
parsed = JSON.parse(result);
|
|
954
|
+
} catch (e) {
|
|
955
|
+
const listResult = parseLispRecursive(result);
|
|
956
|
+
if (Array.isArray(listResult)) {
|
|
957
|
+
parsed = listResult;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (!Array.isArray(parsed)) parsed = [];
|
|
962
|
+
|
|
963
|
+
const dwgList = parsed.map(item => {
|
|
964
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
965
|
+
return { name: String(item[0]), path: String(item[1]) };
|
|
966
|
+
}
|
|
967
|
+
return null;
|
|
968
|
+
}).filter(Boolean);
|
|
969
|
+
|
|
970
|
+
setCache(cacheKey, dwgList);
|
|
971
|
+
return dwgList;
|
|
972
|
+
} catch (e) {
|
|
973
|
+
return [];
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function loadStandardsFile(filename) {
|
|
978
|
+
const filePath = path.join(STANDARDS_DIR, filename);
|
|
979
|
+
try {
|
|
980
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
981
|
+
return JSON.parse(raw);
|
|
982
|
+
} catch (e) {
|
|
983
|
+
log(`Error loading standards file ${filename}: ${e.message}`);
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const standardsCache = new Map();
|
|
989
|
+
|
|
990
|
+
function getCachedStandards(key) {
|
|
991
|
+
const entry = standardsCache.get(key);
|
|
992
|
+
if (entry && Date.now() - entry.timestamp < 30000) return entry.data;
|
|
993
|
+
standardsCache.delete(key);
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function setCachedStandards(key, data) {
|
|
998
|
+
standardsCache.set(key, { data, timestamp: Date.now() });
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function getDraftingStandards() {
|
|
1002
|
+
const cached = getCachedStandards('drafting');
|
|
1003
|
+
if (cached) return cached;
|
|
1004
|
+
const data = loadStandardsFile('drafting.json');
|
|
1005
|
+
if (data) setCachedStandards('drafting', data);
|
|
1006
|
+
return data || { error: '制图规范文件加载失败' };
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function getCodingStandards() {
|
|
1010
|
+
const cached = getCachedStandards('coding');
|
|
1011
|
+
if (cached) return cached;
|
|
1012
|
+
const data = loadStandardsFile('coding.json');
|
|
1013
|
+
if (data) setCachedStandards('coding', data);
|
|
1014
|
+
return data || { error: '编码规范文件加载失败' };
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
async function getCadEvents(params) {
|
|
1018
|
+
await ensureCad();
|
|
1019
|
+
try {
|
|
1020
|
+
const code = `(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"))))`;
|
|
1021
|
+
const result = await cad.sendCommandWithResult(code);
|
|
1022
|
+
const data = {};
|
|
1023
|
+
try {
|
|
1024
|
+
const parsed = JSON.parse(result);
|
|
1025
|
+
if (Array.isArray(parsed)) {
|
|
1026
|
+
for (const pair of parsed) {
|
|
1027
|
+
if (Array.isArray(pair) && pair.length >= 2) data[String(pair[0])] = pair[1];
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
} catch { data.raw = result; }
|
|
1031
|
+
return {
|
|
1032
|
+
entityCount: data.entityCount || 0,
|
|
1033
|
+
dwgName: data.dwgName || '',
|
|
1034
|
+
dwgPath: data.dwgPath || '',
|
|
1035
|
+
timestamp: new Date().toISOString()
|
|
1036
|
+
};
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
return { entityCount: 0, dwgName: '', dwgPath: '', error: e.message };
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// =============================================================================
|
|
1043
|
+
// Live CAD Snapshot (for listResources enrichment)
|
|
1044
|
+
// =============================================================================
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Fetches a lightweight snapshot of current CAD state.
|
|
1048
|
+
* Returns a flat object with entity count, layer names, DWG info, etc.
|
|
1049
|
+
*/
|
|
1050
|
+
async function getDwgContext() {
|
|
1051
|
+
const snapshot = await getCadSnapshot();
|
|
1052
|
+
if (!snapshot || !snapshot.cadConnected) {
|
|
1053
|
+
return { connected: false, message: 'CAD 未连接' };
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
const code = `(progn
|
|
1057
|
+
(vl-load-com)
|
|
1058
|
+
(setq ctx nil)
|
|
1059
|
+
(setq ctx (cons (cons "layout" (getvar "ctab")) ctx))
|
|
1060
|
+
(setq ctx (cons (cons "ucs" (getvar "ucsname")) ctx))
|
|
1061
|
+
(setq ctx (cons (cons "insunits" (getvar "insunits")) ctx))
|
|
1062
|
+
(setq ctx (cons (cons "ltscale" (getvar "ltscale")) ctx))
|
|
1063
|
+
(setq ctx (cons (cons "limmin" (vl-princ-to-string (getvar "limmin"))) ctx))
|
|
1064
|
+
(setq ctx (cons (cons "limmax" (vl-princ-to-string (getvar "limmax"))) ctx))
|
|
1065
|
+
(setq ctx (cons (cons "extmin" (vl-princ-to-string (getvar "extmin"))) ctx))
|
|
1066
|
+
(setq ctx (cons (cons "extmax" (vl-princ-to-string (getvar "extmax"))) ctx))
|
|
1067
|
+
(vl-prin1-to-string ctx))`;
|
|
1068
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
1069
|
+
const parsed = parseLispRecursive(resultStr);
|
|
1070
|
+
return {
|
|
1071
|
+
connected: true,
|
|
1072
|
+
docName: snapshot.dwgName,
|
|
1073
|
+
docPath: snapshot.dwgPath,
|
|
1074
|
+
platform: snapshot.platform,
|
|
1075
|
+
entityCount: snapshot.entityCount,
|
|
1076
|
+
layerCount: snapshot.layerCount,
|
|
1077
|
+
layerNames: snapshot.layerNames,
|
|
1078
|
+
extra: parsed && Array.isArray(parsed) ? Object.fromEntries(parsed) : {},
|
|
1079
|
+
};
|
|
1080
|
+
} catch {
|
|
1081
|
+
return {
|
|
1082
|
+
connected: true,
|
|
1083
|
+
docName: snapshot.dwgName,
|
|
1084
|
+
docPath: snapshot.dwgPath,
|
|
1085
|
+
platform: snapshot.platform,
|
|
1086
|
+
entityCount: snapshot.entityCount,
|
|
1087
|
+
layerCount: snapshot.layerCount,
|
|
1088
|
+
layerNames: snapshot.layerNames,
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function getToolRelationships() {
|
|
1094
|
+
return {
|
|
1095
|
+
selection: {
|
|
1096
|
+
tools: ['select_entities', 'select_all', 'select_last', 'select_by_color', 'find_entities_in_window', 'find_entities_near_point'],
|
|
1097
|
+
next: ['batch_delete', 'batch_move', 'batch_copy', 'batch_set_layer', 'batch_set_color', 'batch_set_linetype', 'batch_rotate', 'batch_scale', 'batch_mirror', 'batch_explode', 'batch_match_properties'],
|
|
1098
|
+
},
|
|
1099
|
+
drawing: {
|
|
1100
|
+
tools: ['draw_line', 'draw_circle', 'draw_arc', 'draw_rectangle', 'draw_polygon', 'draw_polyline', 'draw_spline', 'draw_text', 'draw_mtext', 'draw_ellipse'],
|
|
1101
|
+
next: ['get_entity', 'set_entity', 'batch_match_properties', 'copy_entity', 'move_entity', 'rotate_entity', 'scale_entity'],
|
|
1102
|
+
},
|
|
1103
|
+
editing: {
|
|
1104
|
+
tools: ['trim_entity', 'extend_entity', 'fillet', 'chamfer', 'break_entity', 'join_entities', 'stretch', 'offset_entity', 'mirror_entity'],
|
|
1105
|
+
next: ['get_entity', 'set_entity'],
|
|
1106
|
+
},
|
|
1107
|
+
batch_ops: {
|
|
1108
|
+
tools: ['batch_copy', 'batch_move', 'batch_delete', 'batch_set_layer', 'batch_set_color', 'batch_explode', 'batch_rotate', 'batch_scale'],
|
|
1109
|
+
previous: ['select_entities', 'select_all'],
|
|
1110
|
+
next: ['save_dwg'],
|
|
1111
|
+
},
|
|
1112
|
+
export: {
|
|
1113
|
+
tools: ['export_pdf', 'export_dxf', 'export_dwf', 'export_svg', 'export_stl', 'export_bmp'],
|
|
1114
|
+
previous: ['open_dwg', 'new_document', 'save_dwg'],
|
|
1115
|
+
next: [],
|
|
1116
|
+
},
|
|
1117
|
+
layers: {
|
|
1118
|
+
tools: ['create_layer', 'set_layer', 'layer_isolate', 'layer_freeze_all', 'layer_thaw_all', 'layer_lock_all', 'layer_unlock_all', 'layer_merge'],
|
|
1119
|
+
next: ['set_current_layer', 'batch_set_layer'],
|
|
1120
|
+
},
|
|
1121
|
+
blocks: {
|
|
1122
|
+
tools: ['get_blocks', 'insert_block', 'explode_block', 'get_block_attributes', 'set_block_attribute', 'get_dynamic_block_properties'],
|
|
1123
|
+
previous: ['select_entities'],
|
|
1124
|
+
next: ['save_dwg'],
|
|
1125
|
+
},
|
|
1126
|
+
analysis: {
|
|
1127
|
+
tools: ['analyze_lisp_code', 'stream_entities', 'stream_text', 'stream_block_refs', 'get_entity_info', 'get_bounding_box', 'measure_distance', 'measure_area'],
|
|
1128
|
+
previous: [],
|
|
1129
|
+
next: [],
|
|
1130
|
+
},
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
async function getCadSnapshot() {
|
|
1135
|
+
const cached = getCached('cad:snapshot');
|
|
1136
|
+
if (cached) return cached;
|
|
1137
|
+
|
|
1138
|
+
if (!cad.connected) {
|
|
1139
|
+
await cad.connect();
|
|
1140
|
+
}
|
|
1141
|
+
if (!cad.connected) {
|
|
1142
|
+
return {
|
|
1143
|
+
cadConnected: false,
|
|
1144
|
+
dwgName: null,
|
|
1145
|
+
dwgPath: null,
|
|
1146
|
+
entityCount: 0,
|
|
1147
|
+
layerCount: 0,
|
|
1148
|
+
platform: null,
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
try {
|
|
1153
|
+
const code = `(progn
|
|
1154
|
+
(vl-load-com)
|
|
1155
|
+
(setq sn nil)
|
|
1156
|
+
(setq sn (cons (cons "dwgName" (vl-princ-to-string (getvar "dwgname"))) sn))
|
|
1157
|
+
(setq sn (cons (cons "dwgPath" (vl-princ-to-string (strcat (getvar "dwgprefix") (getvar "dwgname")))) sn))
|
|
1158
|
+
(if (setq ss (ssget "_X"))
|
|
1159
|
+
(setq sn (cons (cons "entityCount" (sslength ss)) sn))
|
|
1160
|
+
(setq sn (cons (cons "entityCount" 0) sn)))
|
|
1161
|
+
(setq lcount 0 lnames nil)
|
|
1162
|
+
(setq le (tblnext "layer" t))
|
|
1163
|
+
(while le
|
|
1164
|
+
(setq ln (cdr (assoc 2 le)))
|
|
1165
|
+
(if (/= ln "0") (setq lnames (cons ln lnames)))
|
|
1166
|
+
(setq lcount (1+ lcount))
|
|
1167
|
+
(setq le (tblnext "layer" nil)))
|
|
1168
|
+
(setq sn (cons (cons "layerCount" lcount) sn))
|
|
1169
|
+
(setq sn (cons (cons "layerNames" lnames) sn))
|
|
1170
|
+
(vl-prin1-to-string sn))`;
|
|
1171
|
+
|
|
1172
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
1173
|
+
const parsed = parseLispRecursive(resultStr);
|
|
1174
|
+
|
|
1175
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
1176
|
+
return {
|
|
1177
|
+
cadConnected: true,
|
|
1178
|
+
dwgName: null,
|
|
1179
|
+
dwgPath: null,
|
|
1180
|
+
entityCount: 0,
|
|
1181
|
+
layerCount: 0,
|
|
1182
|
+
platform: cad.getPlatform(),
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const snapshot = {};
|
|
1187
|
+
for (const pair of parsed) {
|
|
1188
|
+
if (Array.isArray(pair) && pair.length >= 2) {
|
|
1189
|
+
const key = String(pair[0]);
|
|
1190
|
+
const val = pair[1];
|
|
1191
|
+
if (key === 'layerNames' && Array.isArray(val)) {
|
|
1192
|
+
snapshot[key] = val.map(v => String(v));
|
|
1193
|
+
} else if (key === 'entityCount' || key === 'layerCount') {
|
|
1194
|
+
snapshot[key] = typeof val === 'number' ? val : parseInt(val, 10) || 0;
|
|
1195
|
+
} else {
|
|
1196
|
+
snapshot[key] = val !== null && val !== undefined ? String(val) : null;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
snapshot.cadConnected = true;
|
|
1202
|
+
snapshot.platform = cad.getPlatform();
|
|
1203
|
+
|
|
1204
|
+
setCache('cad:snapshot', snapshot);
|
|
1205
|
+
return snapshot;
|
|
1206
|
+
} catch (e) {
|
|
1207
|
+
log(`getCadSnapshot error: ${e.message}`);
|
|
1208
|
+
return {
|
|
1209
|
+
cadConnected: true,
|
|
1210
|
+
dwgName: null,
|
|
1211
|
+
dwgPath: null,
|
|
1212
|
+
entityCount: 0,
|
|
1213
|
+
layerCount: 0,
|
|
1214
|
+
platform: cad.getPlatform(),
|
|
1215
|
+
error: e.message,
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// =============================================================================
|
|
1221
|
+
// URI Parsing Helpers
|
|
1222
|
+
// =============================================================================
|
|
20
1223
|
|
|
21
1224
|
function parseQuery(uri) {
|
|
22
1225
|
const qIdx = uri.indexOf('?');
|
|
@@ -45,16 +1248,71 @@ function parseTemplateUri(uri) {
|
|
|
45
1248
|
return null;
|
|
46
1249
|
}
|
|
47
1250
|
|
|
1251
|
+
// =============================================================================
|
|
1252
|
+
// Public API
|
|
1253
|
+
// =============================================================================
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Returns the current state of resources with live CAD data embedded.
|
|
1257
|
+
* Queries CAD for entity counts, layer names, DWG info, etc. and enriches
|
|
1258
|
+
* resource descriptors with actual data in description and annotations fields.
|
|
1259
|
+
*/
|
|
48
1260
|
export async function listResources(subscribedUris = []) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
1261
|
+
let snapshot = null;
|
|
1262
|
+
|
|
1263
|
+
// Attempt to get a live CAD snapshot (fast, non-blocking on failure)
|
|
1264
|
+
try {
|
|
1265
|
+
const isConnected = cad.connected;
|
|
1266
|
+
if (isConnected) {
|
|
1267
|
+
snapshot = await getCadSnapshot();
|
|
1268
|
+
}
|
|
1269
|
+
} catch {
|
|
1270
|
+
snapshot = null;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const hasLive = snapshot && snapshot.cadConnected;
|
|
1274
|
+
|
|
1275
|
+
return RESOURCES.map(r => {
|
|
1276
|
+
const entry = {
|
|
1277
|
+
uri: r.uri,
|
|
1278
|
+
name: r.name,
|
|
1279
|
+
description: r.description,
|
|
1280
|
+
mimeType: r.mimeType,
|
|
1281
|
+
subscribed: subscribedUris.includes(r.uri),
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
if (hasLive) {
|
|
1285
|
+
switch (r.uri) {
|
|
1286
|
+
case 'atlisp://cad/info':
|
|
1287
|
+
entry.description = `CAD 连接信息 (${snapshot.platform || '未知平台'}${snapshot.dwgName ? ', ' + snapshot.dwgName : ''})`;
|
|
1288
|
+
break;
|
|
1289
|
+
case 'atlisp://dwg/entities':
|
|
1290
|
+
entry.description = `图元列表。${snapshot.entityCount} 个实体,${snapshot.layerCount} 个图层。支持 ?type=LINE&layer=0&bbox=0,0,100,100`;
|
|
1291
|
+
break;
|
|
1292
|
+
case 'atlisp://dwg/name':
|
|
1293
|
+
entry.description = `当前 DWG 文件名: ${snapshot.dwgName || '(未命名)'}`;
|
|
1294
|
+
break;
|
|
1295
|
+
case 'atlisp://dwg/path':
|
|
1296
|
+
entry.description = snapshot.dwgPath ? `文件完整路径: ${snapshot.dwgPath}` : '文件完整路径';
|
|
1297
|
+
break;
|
|
1298
|
+
case 'atlisp://dwg/tbl':
|
|
1299
|
+
entry.description = `CAD 表数据 (${snapshot.layerCount} 个图层)。支持路径方式 atlisp://dwg/tbl/layer`;
|
|
1300
|
+
break;
|
|
1301
|
+
case 'atlisp://dwg/context':
|
|
1302
|
+
entry.description = `当前图纸上下文: ${snapshot.dwgName || '(未命名)'},${snapshot.entityCount} 实体,${snapshot.layerCount} 图层`;
|
|
1303
|
+
break;
|
|
1304
|
+
default: break;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
return entry;
|
|
1309
|
+
});
|
|
56
1310
|
}
|
|
57
1311
|
|
|
1312
|
+
/**
|
|
1313
|
+
* Reads a resource by URI.
|
|
1314
|
+
* Supports both static URIs and template URIs with path parameters.
|
|
1315
|
+
*/
|
|
58
1316
|
export async function readResource(uri) {
|
|
59
1317
|
const templateMatch = parseTemplateUri(uri);
|
|
60
1318
|
if (templateMatch) {
|
|
@@ -123,6 +1381,10 @@ export async function readResource(uri) {
|
|
|
123
1381
|
return getCodingStandards();
|
|
124
1382
|
case 'atlisp://cad/events':
|
|
125
1383
|
return await getCadEvents(params);
|
|
1384
|
+
case 'atlisp://dwg/context':
|
|
1385
|
+
return await getDwgContext();
|
|
1386
|
+
case 'atlisp://tools/relationships':
|
|
1387
|
+
return getToolRelationships();
|
|
126
1388
|
default:
|
|
127
1389
|
throw new Error(`资源不存在: ${uri}`);
|
|
128
1390
|
}
|