Files
envi-code/SourceCode/Code2026/cad_mcp_plugins/mcp_plugins.cpp
T
company-desktop ecb8436720 feat: 移植 cad_mcp_plugins,并对齐中望平台的符号与依赖
插件工程在 CadProject/src/cad_mcp_plugins(8 个 cpp/h),本次一并整合进 code2026。

移植改动:
- 源码拷到 SourceCode/Code2026/cad_mcp_plugins/;cadsdk.h -> KTCad.h
- 输出是普通 DLL(.dll),由 cad_mcp_frame 运行时 LoadLibrary,不注册 ARX 命令
- 新增 cad_mcp_plugins/cmakelists.txt:按 code2026 惯例
  (target_set_name_and_suffix ".dll" / target_link_objectarx / 预编译头 / SUB_ARX)
- mcp_plugin_api.h 从 cad_mcp_frame/inc 上移到 SourceCode/Code2026/inc:
  frame 与 plugins 共用,且在 ShsGlobalIncludes 的 include 路径里两边都可见;
  frame 的 CMake 去掉已删除的本地 inc 目录
- 顶层在同一个 C++11+ 条件里挂载 add_custom_module_2(cad_mcp_plugins CadMcpPlugins)
- 中望适配:源码里的 #pragma comment(lib,"rxapi.lib") 在中望下没有这个库(叫 ZwRx.lib),
  该 pragma 与导出名都按 #ifdef ZWCAD 分开(中望导出 zcrxGetApiVersion);
  frame 里 GetProcAddress 查插件版本号的函数名同样按平台分

验证:
- R230:cad_mcp_frame.arx 826 KB + cad_mcp_plugins.dll 866 KB,已自动部署
- Z2026:cad_mcp_frame.zrx 825 KB + cad_mcp_plugins.dll 864 KB,已自动部署
- 配置里 16 个 c++_dll 工具的工厂函数(CreateXxxTool)全部出现在 dll 导出表里
- 所有 .cpp/.h 为 UTF-8 with BOM

未做:没有在 CAD 里实跑。另外配置文件本身没有随构建部署(set_debug_post_build 只拷产物),
需要把 cad-mcp-frame.json(或改名 mcp_config.json)与 LISP 目录放到部署目录,
否则 frame 起来后 LoadAndRegister 失败会直接自关。详见 cad_mcp_frame-review.md。
2026-10-29 00:16:37 +08:00

166 lines
4.6 KiB
C++

#include "KTCad.h"
#include "mcp_plugin_api.h"
#include <Windows.h>
#include "tchar.h"
#include "rxregsvc.h"
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
#ifdef ZWCAD
// 中望的库是 ZwRx.lib,符号也换成了 zcrx 家族(acrxEntryPoint 会被 SDK 宏改名,
// 但显式写出来的 GetApiVersion 不会,所以这里按平台分开)。
#pragma comment(linker, "/export:zcrxGetApiVersion,PRIVATE")
#else
#pragma comment(lib, "rxapi.lib")
#pragma comment(linker, "/export:acrxGetApiVersion,PRIVATE")
#endif
namespace
{
// 辅助函数:Base64 编码
std::string Base64Encode(const unsigned char *data, size_t length)
{
static const char encoding_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t out_len = 4 * ((length + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char *>(ret.c_str());
for (i = 0; i < length - 2; i += 3)
{
*p++ = encoding_table[(data[i] >> 2) & 0x3F];
*p++ = encoding_table[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
*p++ = encoding_table[((data[i + 1] & 0xF) << 2) | ((int)(data[i + 2] & 0xC0) >> 6)];
*p++ = encoding_table[data[i + 2] & 0x3F];
}
if (i < length)
{
*p++ = encoding_table[(data[i] >> 2) & 0x3F];
if (i == (length - 1))
{
*p++ = encoding_table[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else
{
*p++ = encoding_table[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
*p++ = encoding_table[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
// 辅助函数:获取 GDI+ 图像编码器的 Clsid (用于转 PNG)
int GetEncoderClsid(const WCHAR *format, CLSID *pClsid)
{
UINT num = 0, size = 0;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) return -1;
Gdiplus::ImageCodecInfo *pImageCodecInfo = (Gdiplus::ImageCodecInfo *)(malloc(size));
if (pImageCodecInfo == NULL) return -1;
Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (_tcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
}
class mcp_Tool_ViewportScreenshot: public mcp_Tool
{
public:
virtual nlohmann::json Execute(const nlohmann::json &args) override
{
// 1. 获取 AutoCAD 当前文档的绘图区句柄
HWND hWnd = adsw_acadDocWnd();
if (!hWnd)
return mcp_Tool_Utility::make_error(-32000, L"无法获取CAD绘图区窗口句柄");
// 2. 初始化 GDI+
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
std::string base64Result;
bool success = false;
// 3. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期)
{
RECT rc;
GetClientRect(hWnd, &rc);
int width = rc.right - rc.left;
int height = rc.bottom - rc.top;
HDC hdcScreen = GetDC(hWnd);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, width, height);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, hBitmap);
// 将屏幕内容块拷贝到内存位图中
BitBlt(hdcMem, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY);
// 将 GDI 位图转换为 GDI+ 位图
Gdiplus::Bitmap bmp(hBitmap, NULL);
// 创建内存流,将位图以 PNG 格式保存到内存,而非硬盘
IStream *pStream = nullptr;
if (CreateStreamOnHGlobal(NULL, TRUE, &pStream) == S_OK)
{
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
if (bmp.Save(pStream, &pngClsid, NULL) == Gdiplus::Ok)
{
// 从内存流中读取二进制数据
STATSTG stat;
pStream->Stat(&stat, STATFLAG_NONAME);
ULONG size = stat.cbSize.LowPart;
std::vector<BYTE> buffer(size);
LARGE_INTEGER liZero = {};
pStream->Seek(liZero, STREAM_SEEK_SET, NULL);
ULONG bytesRead;
pStream->Read(buffer.data(), size, &bytesRead);
// 转换为 Base64
base64Result = Base64Encode(buffer.data(), size);
success = true;
}
pStream->Release();
}
// 清理 GDI 资源
SelectObject(hdcMem, hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hdcMem);
ReleaseDC(hWnd, hdcScreen);
}
// 4. 关闭 GDI+
Gdiplus::GdiplusShutdown(gdiplusToken);
// 5. 按照 MCP 多模态协议返回数据
if (success)
{
return mcp_Tool_Utility::make_mixed_result(L"已成功截取 AutoCAD 当前视口画面。", base64Result, "image/png");
}
else
{
return mcp_Tool_Utility::make_error(-32001, L"截图或图像编码失败");
}
}
};
EXPORT_MCP_TOOL(mcp_Tool_ViewportScreenshot, CreateViewportScreenshotTool)