@atlisp/mcp 1.0.10 → 1.0.12
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/README.md +18 -0
- package/package.json +4 -4
- package/src/atlisp-mcp.js +304 -0
- package/src/cad-worker.js +91 -91
- package/src/cad.js +46 -19
- package/src/cli.js +1 -1
- package/src/constants.js +22 -0
- package/src/handlers/cad-handlers.js +69 -0
- package/src/handlers/function-handlers.js +109 -0
- package/src/handlers/package-handlers.js +26 -0
- package/src/logger.js +30 -0
- package/src/sse-utils.js +32 -0
- package/src/index.js +0 -379
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ MCP (Model Context Protocol) Server for @lisp on CAD - 为 CAD 环境提供 @lis
|
|
|
4
4
|
|
|
5
5
|
同时为 AI AGENT 操控 cad 提供调用工具和通道。
|
|
6
6
|
|
|
7
|
+
## 版本
|
|
8
|
+
|
|
9
|
+
**当前版本**: 1.0.10
|
|
10
|
+
|
|
7
11
|
## 功能特性
|
|
8
12
|
|
|
9
13
|
- 连接 AutoCAD/ZWCAD/GStarCAD/BricsCAD
|
|
@@ -126,6 +130,20 @@ atlisp-mcp
|
|
|
126
130
|
| `MESSAGE_TIMEOUT` | `60000` | 消息超时(毫秒) |
|
|
127
131
|
| `BUSY_RETRIES` | `10` | CAD 忙碌时重试次数 |
|
|
128
132
|
| `BUSY_DELAY` | `500` | CAD 忙碌重试间隔(毫秒) |
|
|
133
|
+
| `MCP_API_KEY` | (无) | API Key 认证(可选) |
|
|
134
|
+
|
|
135
|
+
### 运行测试
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm test
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
测试覆盖 54 个用例,包括:
|
|
142
|
+
- 工具定义完整性
|
|
143
|
+
- 连接/断开 CAD
|
|
144
|
+
- 执行 LISP 代码
|
|
145
|
+
- 包管理操作
|
|
146
|
+
- 错误处理
|
|
129
147
|
|
|
130
148
|
## API
|
|
131
149
|
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlisp/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "MCP Server for @lisp on CAD",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"atlisp-mcp": "./src/cli.js"
|
|
8
8
|
},
|
|
9
|
-
"main": "src/
|
|
9
|
+
"main": "src/atlisp-mcp.js",
|
|
10
10
|
"exports": {
|
|
11
|
-
".": "./src/
|
|
11
|
+
".": "./src/atlisp-mcp.js",
|
|
12
12
|
"./cad": "./src/cad.js"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"src"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"start": "node src/
|
|
18
|
+
"start": "node src/atlisp-mcp.js",
|
|
19
19
|
"test": "vitest run",
|
|
20
20
|
"test:watch": "vitest"
|
|
21
21
|
},
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import {
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { cad } from './cad.js';
|
|
10
|
+
import { CAD_PLATFORMS, FILE_EXTENSIONS, PROTOCOL_VERSION, SERVER_NAME, SERVER_VERSION } from './constants.js';
|
|
11
|
+
import { log } from './logger.js';
|
|
12
|
+
import * as cadHandlers from './handlers/cad-handlers.js';
|
|
13
|
+
import * as packageHandlers from './handlers/package-handlers.js';
|
|
14
|
+
import * as functionHandlers from './handlers/function-handlers.js';
|
|
15
|
+
import { createSseResponseHandler } from './sse-utils.js';
|
|
16
|
+
|
|
17
|
+
const PORT = process.env.PORT || 8110;
|
|
18
|
+
const HOST = process.env.HOST || '0.0.0.0';
|
|
19
|
+
const TRANSPORT = process.env.TRANSPORT || 'http';
|
|
20
|
+
const API_KEY = process.env.MCP_API_KEY || '';
|
|
21
|
+
if (API_KEY) log('API Key authentication enabled');
|
|
22
|
+
|
|
23
|
+
export { CAD_PLATFORMS, FILE_EXTENSIONS, MOCK_PACKAGES } from './constants.js';
|
|
24
|
+
|
|
25
|
+
const TOOL_HANDLERS = {
|
|
26
|
+
connect_cad: () => cadHandlers.connectCad(),
|
|
27
|
+
eval_lisp: (a) => cadHandlers.evalLisp(a.code),
|
|
28
|
+
eval_lisp_with_result: (a) => cadHandlers.evalLisp(a.code, true),
|
|
29
|
+
get_cad_info: () => cadHandlers.getCadInfo(),
|
|
30
|
+
list_packages: () => packageHandlers.listPackages(),
|
|
31
|
+
search_packages: (a) => packageHandlers.searchPackages(a.query),
|
|
32
|
+
install_package: (a) => packageHandlers.installPackage(a.packageName),
|
|
33
|
+
get_platform_info: () => ({ content: [{ type: 'text', text: `支持的 CAD 平台:\n${CAD_PLATFORMS.join('\n')}\n当前连接: ${cad.connected ? '已连接' : '未连接'}` }] }),
|
|
34
|
+
get_file_extensions: (a) => {
|
|
35
|
+
const exts = FILE_EXTENSIONS[a.platform];
|
|
36
|
+
if (!exts) return { content: [{ type: 'text', text: `错误: 不支持的平台 "${a.platform}"` }], isError: true };
|
|
37
|
+
return { content: [{ type: 'text', text: `${a.platform} 文件扩展名: ${exts.join(', ')}` }] };
|
|
38
|
+
},
|
|
39
|
+
get_install_code: () => ({ content: [{ type: 'text', text: `复制以下代码到 CAD 命令行安装 @lisp:\n\n(progn(vl-load-com)(setq s strcat h "http" o(vlax-create-object (s"win"h".win"h"request.5.1"))v vlax-invoke e eval r read)(v o'open "get" (s h"://atlisp.""cn/@"):vlax-true)(v o'send)(v o'WaitforResponse 1000)(e(r(vlax-get-property o'ResponseText))))` }] }),
|
|
40
|
+
at_command: (a) => cadHandlers.atCommand(a.command),
|
|
41
|
+
new_document: () => cadHandlers.newDocument(),
|
|
42
|
+
install_atlisp: () => cadHandlers.installAtlisp(),
|
|
43
|
+
get_function_usage: (a) => functionHandlers.getFunctionUsage(a.name, a.package),
|
|
44
|
+
list_functions: (a) => functionHandlers.listFunctions(a.package),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const tools = [
|
|
48
|
+
{
|
|
49
|
+
name: 'connect_cad',
|
|
50
|
+
description: '连接到 CAD (AutoCAD/ZWCAD/GStarCAD/BricsCAD)',
|
|
51
|
+
inputSchema: { type: 'object', properties: {} }
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'eval_lisp',
|
|
55
|
+
description: '在 CAD 中执行 AutoLISP 代码(不返回结果)',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
|
|
59
|
+
required: ['code']
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'eval_lisp_with_result',
|
|
64
|
+
description: '在 CAD 中执行 AutoLISP 代码并返回结果',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
|
|
68
|
+
required: ['code']
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'get_cad_info',
|
|
73
|
+
description: '获取当前 CAD 信息',
|
|
74
|
+
inputSchema: { type: 'object', properties: {} }
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'list_packages',
|
|
78
|
+
description: '列出已安装的 @lisp 包',
|
|
79
|
+
inputSchema: { type: 'object', properties: {} }
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'search_packages',
|
|
83
|
+
description: '搜索 @lisp 包',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: { query: { type: 'string', description: '搜索关键词' } },
|
|
87
|
+
required: ['query']
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'install_package',
|
|
92
|
+
description: '安装 @lisp 包到 CAD',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: { packageName: { type: 'string', description: '包名称' } },
|
|
96
|
+
required: ['packageName']
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'get_platform_info',
|
|
101
|
+
description: '获取 CAD 平台信息',
|
|
102
|
+
inputSchema: { type: 'object', properties: {} }
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'get_file_extensions',
|
|
106
|
+
description: '获取指定平台的文件扩展名',
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: { platform: { type: 'string', enum: CAD_PLATFORMS, description: 'CAD 平台' } },
|
|
110
|
+
required: ['platform']
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'get_install_code',
|
|
115
|
+
description: '获取 @lisp 安装代码',
|
|
116
|
+
inputSchema: { type: 'object', properties: {} }
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'at_command',
|
|
120
|
+
description: '执行 @lisp 命令',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: { command: { type: 'string', description: '@lisp 命令' } },
|
|
124
|
+
required: ['command']
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'new_document',
|
|
129
|
+
description: '在 CAD 中新建空白文档',
|
|
130
|
+
inputSchema: { type: 'object', properties: {} }
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'install_atlisp',
|
|
134
|
+
description: '在 CAD 中安装 @lisp',
|
|
135
|
+
inputSchema: { type: 'object', properties: {} }
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'get_function_usage',
|
|
139
|
+
description: '获取 @lisp 函数用法(从本地 JSON 库)',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
name: { type: 'string', description: '函数名,如 string:length' },
|
|
144
|
+
package: { type: 'string', description: '包名,如 string(可选,不传则自动搜索)' }
|
|
145
|
+
},
|
|
146
|
+
required: ['name']
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'list_functions',
|
|
151
|
+
description: '列出 @lisp 函数库中的所有函数',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
package: { type: 'string', description: '包名,如 string(可选)' }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
export async function handleToolCall(name, args) {
|
|
162
|
+
const handler = TOOL_HANDLERS[name];
|
|
163
|
+
if (!handler) return { content: [{ type: 'text', text: `未知工具: ${name}` }], isError: true };
|
|
164
|
+
return handler(args);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function initCadConnection() {
|
|
168
|
+
log('Attempting to connect to CAD on startup...');
|
|
169
|
+
try {
|
|
170
|
+
const connected = await cad.connect();
|
|
171
|
+
if (connected) {
|
|
172
|
+
log('Auto-connected to CAD: ' + cad.getPlatform() + ' ' + cad.getVersion());
|
|
173
|
+
console.error('已自动连接到 CAD: ' + cad.getPlatform() + ' ' + cad.getVersion());
|
|
174
|
+
} else {
|
|
175
|
+
log('No CAD found on startup');
|
|
176
|
+
}
|
|
177
|
+
} catch (e) {
|
|
178
|
+
log('Auto-connect failed: ' + e.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let server = null;
|
|
183
|
+
|
|
184
|
+
export function createMcpServer() {
|
|
185
|
+
server = new Server(
|
|
186
|
+
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
187
|
+
{ capabilities: { tools: {} } }
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
191
|
+
|
|
192
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
193
|
+
const { name, arguments: args } = request.params;
|
|
194
|
+
return await handleToolCall(name, args || {});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return server;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function startServer() {
|
|
201
|
+
if (TRANSPORT === 'stdio') {
|
|
202
|
+
await initCadConnection();
|
|
203
|
+
const mcpServer = createMcpServer();
|
|
204
|
+
const transport = new StdioServerTransport();
|
|
205
|
+
await mcpServer.connect(transport);
|
|
206
|
+
console.error(`${SERVER_NAME} 运行在 stdio 模式`);
|
|
207
|
+
} else {
|
|
208
|
+
const app = express();
|
|
209
|
+
app.use(express.json({ type: ['application/json', 'application/json-rpc'] }));
|
|
210
|
+
|
|
211
|
+
app.use((req, res, next) => {
|
|
212
|
+
log(`${req.method} ${req.path} - body: ${JSON.stringify(req.body)}`);
|
|
213
|
+
next();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const PUBLIC_PATHS = ['/health'];
|
|
217
|
+
if (API_KEY) {
|
|
218
|
+
app.use((req, res, next) => {
|
|
219
|
+
if (PUBLIC_PATHS.includes(req.path)) return next();
|
|
220
|
+
const auth = req.get('Authorization');
|
|
221
|
+
if (auth === `Bearer ${API_KEY}`) return next();
|
|
222
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
app.get('/health', (req, res) => {
|
|
227
|
+
res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (process.env.ENABLE_CORS) {
|
|
231
|
+
app.use((req, res, next) => {
|
|
232
|
+
res.setHeader('Access-Control-Allow-Origin', process.env.CORS_ORIGIN || '*');
|
|
233
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
234
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept, mcp-session-id');
|
|
235
|
+
if (req.method === 'OPTIONS') return res.status(204).end();
|
|
236
|
+
next();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
createMcpServer();
|
|
241
|
+
|
|
242
|
+
const sessionId = crypto.randomUUID();
|
|
243
|
+
|
|
244
|
+
['/sse', '/mcp/sse'].forEach(p => app.all(p, (req, res) => {
|
|
245
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream' });
|
|
246
|
+
const transport = new SSEServerTransport('/mcp', res);
|
|
247
|
+
server.connect(transport);
|
|
248
|
+
}));
|
|
249
|
+
|
|
250
|
+
app.post('/mcp', async (req, res) => {
|
|
251
|
+
const body = req.body || {};
|
|
252
|
+
const { method, params, id } = body;
|
|
253
|
+
const accept = req.get('Accept') || '';
|
|
254
|
+
const wantsSse = accept.includes('text/event-stream');
|
|
255
|
+
log('accept: "' + accept + '", wantsSSE: ' + wantsSse);
|
|
256
|
+
|
|
257
|
+
res.setHeader('mcp-session-id', sessionId);
|
|
258
|
+
const { send } = createSseResponseHandler(res, wantsSse);
|
|
259
|
+
|
|
260
|
+
if (!id) return res.status(204).end();
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
if (method === 'initialize') {
|
|
264
|
+
send({ jsonrpc: '2.0', id, result: {
|
|
265
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
266
|
+
capabilities: { tools: {} },
|
|
267
|
+
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION }
|
|
268
|
+
} }, true);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (method === 'tools/list') {
|
|
273
|
+
send({ jsonrpc: '2.0', id, result: { tools } }, true);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (method === 'tools/call') {
|
|
278
|
+
const toolName = params?.name;
|
|
279
|
+
if (!toolName) {
|
|
280
|
+
send({ jsonrpc: '2.0', id, error: { code: -32602, message: 'Invalid params: missing tool name' } }, true);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const result = await handleToolCall(toolName, params?.arguments || {});
|
|
284
|
+
send({ jsonrpc: '2.0', id, result }, true);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } }, true);
|
|
289
|
+
} catch (e) {
|
|
290
|
+
log('MCP error: ' + e.message);
|
|
291
|
+
send({ jsonrpc: '2.0', id, error: { code: -32603, message: e.message } }, true);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
app.listen(PORT, HOST, async () => {
|
|
296
|
+
await initCadConnection();
|
|
297
|
+
console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - Streamable HTTP`);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!process.env.VITEST) {
|
|
303
|
+
await startServer();
|
|
304
|
+
}
|
package/src/cad-worker.js
CHANGED
|
@@ -7,103 +7,53 @@ const BUSY_DELAY = parseInt(process.env.BUSY_DELAY || '500', 10);
|
|
|
7
7
|
|
|
8
8
|
const cadConnect = edge.func(function() {/*
|
|
9
9
|
async (input) => {
|
|
10
|
-
// Try existing CAD instances
|
|
11
10
|
try {
|
|
12
11
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("AutoCAD.Application");
|
|
13
12
|
if (cad != null) {
|
|
14
13
|
string ver = cad.Version.ToString();
|
|
15
14
|
return new { success = true, platform = "AutoCAD", version = ver };
|
|
16
15
|
}
|
|
17
|
-
} catch {
|
|
16
|
+
} catch (Exception ex) {
|
|
17
|
+
System.Diagnostics.Debug.WriteLine("AutoCAD connect error: " + ex.Message);
|
|
18
|
+
}
|
|
18
19
|
try {
|
|
19
20
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("ZWCAD.Application");
|
|
20
21
|
if (cad != null) {
|
|
21
22
|
string ver = cad.Version.ToString();
|
|
22
23
|
return new { success = true, platform = "ZWCAD", version = ver };
|
|
23
24
|
}
|
|
24
|
-
} catch {
|
|
25
|
+
} catch (Exception ex) {
|
|
26
|
+
System.Diagnostics.Debug.WriteLine("ZWCAD connect error: " + ex.Message);
|
|
27
|
+
}
|
|
25
28
|
try {
|
|
26
29
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("GStarCAD.Application");
|
|
27
30
|
if (cad != null) {
|
|
28
31
|
string ver = cad.Version.ToString();
|
|
29
32
|
return new { success = true, platform = "GStarCAD", version = ver };
|
|
30
33
|
}
|
|
31
|
-
} catch {
|
|
34
|
+
} catch (Exception ex) {
|
|
35
|
+
System.Diagnostics.Debug.WriteLine("GStarCAD connect error: " + ex.Message);
|
|
36
|
+
}
|
|
32
37
|
try {
|
|
33
38
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("BricsCAD.Application");
|
|
34
39
|
if (cad != null) {
|
|
35
40
|
string ver = cad.Version.ToString();
|
|
36
41
|
return new { success = true, platform = "BricsCAD", version = ver };
|
|
37
42
|
}
|
|
38
|
-
} catch {
|
|
39
|
-
|
|
40
|
-
// Try to create new CAD instance
|
|
41
|
-
string[] cadPaths = new string[] {
|
|
42
|
-
@"C:\Program Files\Autodesk\AutoCAD 2022\acad.exe",
|
|
43
|
-
@"C:\Program Files\Autodesk\AutoCAD 2024\acad.exe",
|
|
44
|
-
@"C:\Program Files\Autodesk\AutoCAD 2023\acad.exe",
|
|
45
|
-
@"C:\Program Files\Autodesk\AutoCAD 2021\acad.exe",
|
|
46
|
-
@"C:\Program Files\Autodesk\AutoCAD 2020\acad.exe",
|
|
47
|
-
@"C:\Program Files\ZWSOFT\ZWCAD 2024\zwcad.exe",
|
|
48
|
-
@"C:\Program Files\ZWSOFT\ZWCAD 2023\zwcad.exe",
|
|
49
|
-
@"C:\Program Files\ZWSOFT\ZWCAD 2022\zwcad.exe",
|
|
50
|
-
@"C:\Program Files\ZWSOFT\ZWCAD 2021\zwcad.exe",
|
|
51
|
-
@"C:\Program Files\GSTARSOFT\GStarCAD 2024\GCAD.exe",
|
|
52
|
-
@"C:\Program Files\GSTARSOFT\GStarCAD 2023\GCAD.exe",
|
|
53
|
-
@"C:\Program Files\Bricsys\BricsCAD\V24\BricsCAD.exe",
|
|
54
|
-
@"C:\Program Files\Bricsys\BricsCAD\V23\BricsCAD.exe"
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
foreach (string path in cadPaths) {
|
|
58
|
-
if (System.IO.File.Exists(path)) {
|
|
59
|
-
try {
|
|
60
|
-
System.Diagnostics.Process.Start(path);
|
|
61
|
-
System.Threading.Thread.Sleep(60000);
|
|
62
|
-
|
|
63
|
-
// Try to connect to newly started CAD
|
|
64
|
-
try {
|
|
65
|
-
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("AutoCAD.Application");
|
|
66
|
-
if (cad != null) {
|
|
67
|
-
string ver = cad.Version.ToString();
|
|
68
|
-
return new { success = true, platform = "AutoCAD", version = ver };
|
|
69
|
-
}
|
|
70
|
-
} catch {}
|
|
71
|
-
try {
|
|
72
|
-
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("ZWCAD.Application");
|
|
73
|
-
if (cad != null) {
|
|
74
|
-
string ver = cad.Version.ToString();
|
|
75
|
-
return new { success = true, platform = "ZWCAD", version = ver };
|
|
76
|
-
}
|
|
77
|
-
} catch {}
|
|
78
|
-
try {
|
|
79
|
-
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("GStarCAD.Application");
|
|
80
|
-
if (cad != null) {
|
|
81
|
-
string ver = cad.Version.ToString();
|
|
82
|
-
return new { success = true, platform = "GStarCAD", version = ver };
|
|
83
|
-
}
|
|
84
|
-
} catch {}
|
|
85
|
-
try {
|
|
86
|
-
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("BricsCAD.Application");
|
|
87
|
-
if (cad != null) {
|
|
88
|
-
string ver = cad.Version.ToString();
|
|
89
|
-
return new { success = true, platform = "BricsCAD", version = ver };
|
|
90
|
-
}
|
|
91
|
-
} catch {}
|
|
92
|
-
} catch {}
|
|
93
|
-
}
|
|
43
|
+
} catch (Exception ex) {
|
|
44
|
+
System.Diagnostics.Debug.WriteLine("BricsCAD connect error: " + ex.Message);
|
|
94
45
|
}
|
|
95
|
-
|
|
96
46
|
return new { success = false, error = "No CAD found" };
|
|
97
47
|
}
|
|
98
48
|
*/});
|
|
99
49
|
|
|
100
50
|
const cadSend = edge.func(function() {/*
|
|
101
51
|
async (input) => {
|
|
102
|
-
dynamic d = input;
|
|
103
|
-
string code = d.code.ToString();
|
|
104
|
-
string platform = d.platform.ToString();
|
|
105
|
-
string progId = platform + ".Application";
|
|
106
52
|
try {
|
|
53
|
+
dynamic d = input;
|
|
54
|
+
string code = d.code.ToString();
|
|
55
|
+
string platform = d.platform.ToString();
|
|
56
|
+
string progId = platform + ".Application";
|
|
107
57
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
|
|
108
58
|
if (cad != null) {
|
|
109
59
|
dynamic doc = cad.ActiveDocument;
|
|
@@ -112,49 +62,51 @@ const cadSend = edge.func(function() {/*
|
|
|
112
62
|
return new { success = true };
|
|
113
63
|
}
|
|
114
64
|
}
|
|
115
|
-
} catch {
|
|
65
|
+
} catch (Exception ex) {
|
|
66
|
+
System.Diagnostics.Debug.WriteLine("cadSend error: " + ex.Message);
|
|
67
|
+
}
|
|
116
68
|
return new { success = false };
|
|
117
69
|
}
|
|
118
70
|
*/});
|
|
119
71
|
|
|
120
72
|
const cadSendWithResult = edge.func(function() {/*
|
|
121
73
|
async (input) => {
|
|
122
|
-
|
|
123
|
-
string code = d.code.ToString();
|
|
124
|
-
string platform = d.platform.ToString();
|
|
125
|
-
string progId = platform + ".Application";
|
|
126
|
-
|
|
127
|
-
string tempDir = System.IO.Path.GetTempPath();
|
|
128
|
-
string tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
|
|
129
|
-
|
|
130
|
-
// Use print to write result directly to file
|
|
131
|
-
string lispCode = "(progn(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))(print " + code + " f)(close f))";
|
|
132
|
-
|
|
74
|
+
string tempFile = null;
|
|
133
75
|
try {
|
|
76
|
+
dynamic d = input;
|
|
77
|
+
string code = d.code.ToString();
|
|
78
|
+
string platform = d.platform.ToString();
|
|
79
|
+
string progId = platform + ".Application";
|
|
80
|
+
string tempDir = System.IO.Path.GetTempPath();
|
|
81
|
+
tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
|
|
82
|
+
string lispCode = "(progn(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))(print " + code + " f)(close f))";
|
|
134
83
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
|
|
135
84
|
if (cad != null) {
|
|
136
85
|
dynamic doc = cad.ActiveDocument;
|
|
137
86
|
if (doc != null) {
|
|
138
87
|
doc.SendCommand(lispCode + "\n");
|
|
139
88
|
System.Threading.Thread.Sleep(500);
|
|
140
|
-
|
|
141
89
|
if (System.IO.File.Exists(tempFile)) {
|
|
142
|
-
string result = System.IO.File.
|
|
143
|
-
|
|
90
|
+
string result = System.Text.Encoding.GetEncoding("GBK").GetString(System.IO.File.ReadAllBytes(tempFile)).Trim();
|
|
91
|
+
System.IO.File.Delete(tempFile);
|
|
144
92
|
if (!string.IsNullOrEmpty(result)) {
|
|
145
93
|
return new { success = true, result = result };
|
|
146
94
|
}
|
|
147
|
-
// Try evaluate result directly if file is empty
|
|
148
95
|
return new { success = true, result = "" };
|
|
149
96
|
}
|
|
150
97
|
return new { success = true, result = "" };
|
|
151
98
|
}
|
|
152
99
|
}
|
|
153
100
|
} catch (Exception ex) {
|
|
154
|
-
|
|
101
|
+
System.Diagnostics.Debug.WriteLine("cadSendWithResult error: " + ex.Message);
|
|
102
|
+
if (tempFile != null && System.IO.File.Exists(tempFile)) {
|
|
103
|
+
try { System.IO.File.Delete(tempFile); } catch {}
|
|
104
|
+
}
|
|
155
105
|
return new { success = false, error = ex.Message };
|
|
156
106
|
}
|
|
157
|
-
|
|
107
|
+
if (tempFile != null && System.IO.File.Exists(tempFile)) {
|
|
108
|
+
try { System.IO.File.Delete(tempFile); } catch {}
|
|
109
|
+
}
|
|
158
110
|
return new { success = false };
|
|
159
111
|
}
|
|
160
112
|
*/});
|
|
@@ -180,9 +132,9 @@ process.stdin.on('data', (data) => {
|
|
|
180
132
|
|
|
181
133
|
const cadIsBusy = edge.func(function() {/*
|
|
182
134
|
async (input) => {
|
|
183
|
-
string platform = input.ToString();
|
|
184
|
-
string progId = platform + ".Application";
|
|
185
135
|
try {
|
|
136
|
+
string platform = input.ToString();
|
|
137
|
+
string progId = platform + ".Application";
|
|
186
138
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
|
|
187
139
|
if (cad != null) {
|
|
188
140
|
bool isBusy = false;
|
|
@@ -193,37 +145,42 @@ const cadIsBusy = edge.func(function() {/*
|
|
|
193
145
|
}
|
|
194
146
|
return new { isBusy = isBusy };
|
|
195
147
|
}
|
|
196
|
-
} catch {
|
|
148
|
+
} catch (Exception ex) {
|
|
149
|
+
System.Diagnostics.Debug.WriteLine("cadIsBusy error: " + ex.Message);
|
|
150
|
+
}
|
|
197
151
|
return new { isBusy = false };
|
|
198
152
|
}
|
|
199
153
|
*/});
|
|
200
154
|
|
|
201
155
|
const cadHasDoc = edge.func(function() {/*
|
|
202
156
|
async (input) => {
|
|
203
|
-
string platform = input.ToString();
|
|
204
|
-
string progId = platform + ".Application";
|
|
205
157
|
try {
|
|
158
|
+
string platform = input.ToString();
|
|
159
|
+
string progId = platform + ".Application";
|
|
206
160
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
|
|
207
161
|
if (cad != null) {
|
|
208
162
|
int docCount = cad.Documents.Count;
|
|
209
163
|
return new { hasDoc = docCount > 0, docCount = docCount };
|
|
210
164
|
}
|
|
211
|
-
} catch {
|
|
165
|
+
} catch (Exception ex) {
|
|
166
|
+
System.Diagnostics.Debug.WriteLine("cadHasDoc error: " + ex.Message);
|
|
167
|
+
}
|
|
212
168
|
return new { hasDoc = false, docCount = 0 };
|
|
213
169
|
}
|
|
214
170
|
*/});
|
|
215
171
|
|
|
216
172
|
const cadNewDoc = edge.func(function() {/*
|
|
217
173
|
async (input) => {
|
|
218
|
-
string platform = input.ToString();
|
|
219
|
-
string progId = platform + ".Application";
|
|
220
174
|
try {
|
|
175
|
+
string platform = input.ToString();
|
|
176
|
+
string progId = platform + ".Application";
|
|
221
177
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
|
|
222
178
|
if (cad != null) {
|
|
223
179
|
cad.Documents.Add();
|
|
224
180
|
return new { success = true };
|
|
225
181
|
}
|
|
226
182
|
} catch (Exception ex) {
|
|
183
|
+
System.Diagnostics.Debug.WriteLine("cadNewDoc error: " + ex.Message);
|
|
227
184
|
return new { success = false, error = ex.Message };
|
|
228
185
|
}
|
|
229
186
|
return new { success = false, error = "CAD not found" };
|
|
@@ -246,7 +203,42 @@ async function waitForIdle(platform) {
|
|
|
246
203
|
return false;
|
|
247
204
|
}
|
|
248
205
|
|
|
206
|
+
function checkParens(code) {
|
|
207
|
+
let depth = 0;
|
|
208
|
+
let inString = false;
|
|
209
|
+
let escapeNext = false;
|
|
210
|
+
for (let i = 0; i < code.length; i++) {
|
|
211
|
+
const ch = code[i];
|
|
212
|
+
if (escapeNext) {
|
|
213
|
+
escapeNext = false;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (ch === '\\') {
|
|
217
|
+
escapeNext = true;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (ch === '"') {
|
|
221
|
+
inString = !inString;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (inString) continue;
|
|
225
|
+
if (ch === ';') {
|
|
226
|
+
while (i < code.length && code[i] !== '\n') i++;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (ch === '(') depth++;
|
|
230
|
+
if (ch === ')') depth--;
|
|
231
|
+
if (depth < 0) return { valid: false, error: '多余右括号 )', pos: i };
|
|
232
|
+
}
|
|
233
|
+
if (depth > 0) return { valid: false, error: `缺少 ${depth} 个右括号 )`, pos: code.length - 1 };
|
|
234
|
+
if (depth < 0) return { valid: false, error: `多余 ${-depth} 个左括号 (` };
|
|
235
|
+
return { valid: true };
|
|
236
|
+
}
|
|
237
|
+
|
|
249
238
|
async function handleMessage(msg) {
|
|
239
|
+
if (msg.type === 'ping') {
|
|
240
|
+
return { pong: true };
|
|
241
|
+
}
|
|
250
242
|
if (msg.type === 'connect') {
|
|
251
243
|
return new Promise((resolve, reject) => {
|
|
252
244
|
cadConnect({}, (e, r) => {
|
|
@@ -281,6 +273,10 @@ async function handleMessage(msg) {
|
|
|
281
273
|
if (!docCheck.hasDoc) {
|
|
282
274
|
return { success: false, error: 'No open document' };
|
|
283
275
|
}
|
|
276
|
+
const parenCheck = checkParens(msg.code);
|
|
277
|
+
if (!parenCheck.valid) {
|
|
278
|
+
return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
|
|
279
|
+
}
|
|
284
280
|
if (!(await waitForIdle(msg.platform))) {
|
|
285
281
|
return { success: false, error: 'CAD is busy, timeout' };
|
|
286
282
|
}
|
|
@@ -301,6 +297,10 @@ async function handleMessage(msg) {
|
|
|
301
297
|
if (!docCheck.hasDoc) {
|
|
302
298
|
return { success: false, error: 'No open document' };
|
|
303
299
|
}
|
|
300
|
+
const parenCheck = checkParens(msg.code);
|
|
301
|
+
if (!parenCheck.valid) {
|
|
302
|
+
return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
|
|
303
|
+
}
|
|
304
304
|
if (!(await waitForIdle(msg.platform))) {
|
|
305
305
|
return { success: false, error: 'CAD is busy, timeout' };
|
|
306
306
|
}
|