增加了 plugin 工程
This commit is contained in:
gjm committed 2026-10-02 23:43:18 +08:00
1 parent cf87cfe93d
commit a7eaa8483f
11 files changed
+279 -19

No files matched your search

+10
View File
@@ -98,6 +98,15 @@ function (get_sdk_version out)
set(${out} ${VER} PARENT_SCOPE) set(${out} ${VER} PARENT_SCOPE)
endfunction() endfunction()
function (get_sdk_sub_version out)
IsZWCAD(isZW)
set(VER ${CAD_SDK_SUBVERSION})
if (isZW)
set(VER "0")
endif()
set(${out} ${VER} PARENT_SCOPE)
endfunction()
# 根据 ZWCAD宏,来判断需要的def文件名称,保存到表_SOURCES 里面 # 根据 ZWCAD宏,来判断需要的def文件名称,保存到表_SOURCES 里面
function (add_def_file_name function (add_def_file_name
_SOURCES _SOURCES
@@ -204,6 +213,7 @@ function(target_link_objectarx
target_compile_definitions(${TARGET_NAME} PRIVATE target_compile_definitions(${TARGET_NAME} PRIVATE
CMAKE CMAKE
ARX=${SDK_VERSION} ARX=${SDK_VERSION}
SUB_ARX=${SDK_SUBVERSION}
${_EXTENSION} # 指示这是一个 ARX 扩展 ${_EXTENSION} # 指示这是一个 ARX 扩展
) )
endfunction() endfunction()
+4
View File
@@ -24,6 +24,9 @@ IsZWCAD(IS_ZWCAD)
# 如果时中望,取出版本号的后两位,作为宏定义 # 如果时中望,取出版本号的后两位,作为宏定义
get_sdk_version(SDK_VERSION) get_sdk_version(SDK_VERSION)
# 如果时中望,subversion 统一为0,否则取出 subversion
get_sdk_sub_version(SDK_SUBVERSION)
# ThirdPartyDeps里面使用了SDK_VERSION # ThirdPartyDeps里面使用了SDK_VERSION
include(ThirdPartyDeps) include(ThirdPartyDeps)
@@ -136,4 +139,5 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${GLOBAL_LIB_DIR}/Release-${GENERATE
message(STATUS ">>> 正在构建业务树...") message(STATUS ">>> 正在构建业务树...")
add_subdirectory(./src/cad_mcp_frame) add_subdirectory(./src/cad_mcp_frame)
add_subdirectory(./src/cad_mcp_plugins)
+1 -1
View File
@@ -12,7 +12,7 @@ set(SOURCES
"cad_mcp_frame.rc" "cad_mcp_frame.rc"
"mcp_server.cpp" "mcp_server.cpp"
"mcp_tool.cpp" "mcp_tool.cpp"
"mcp_ConfigParser.cpp" "mcp_configParser.cpp"
"mcp_utils.cpp" "mcp_utils.cpp"
) )
add_def_file_name(SOURCES "${PROJECT_NAME}") add_def_file_name(SOURCES "${PROJECT_NAME}")
+45 -2
View File
@@ -4,6 +4,7 @@
#include "mcp_tool.h" #include "mcp_tool.h"
#include "mcp_utils.h" #include "mcp_utils.h"
#include "ghc/filesystem.hpp" #include "ghc/filesystem.hpp"
#include "rxregsvc.h"
#pragma region mcp_ConfigParser #pragma region mcp_ConfigParser
bool mcp_ConfigParser::LoadAndRegister(const std::wstring &jsonFilePath, mcp_Tools &registry) bool mcp_ConfigParser::LoadAndRegister(const std::wstring &jsonFilePath, mcp_Tools &registry)
@@ -50,6 +51,7 @@ bool mcp_ConfigParser::LoadAndRegister(const std::wstring &jsonFilePath, mcp_Too
return true; return true;
} }
extern "C" int acrxGetApiVersion();
void mcp_ConfigParser::LoadDllTool(const nlohmann::json &toolConfig, const std::wstring &pluginDir, mcp_Tools &registry) void mcp_ConfigParser::LoadDllTool(const nlohmann::json &toolConfig, const std::wstring &pluginDir, mcp_Tools &registry)
{ {
fs::path dllPath fs::path dllPath
@@ -59,12 +61,53 @@ void mcp_ConfigParser::LoadDllTool(const nlohmann::json &toolConfig, const std::
if (!fs::exists(dllPath)) if (!fs::exists(dllPath))
{ {
AfxMessageBox(CString(_T("[MCP Server]Warning: The DLL file does not exist; the associated tools will not load!\n")) + dllPath.c_str()); AfxMessageBox(CString(_T("[MCP Server]Warning: The DLL file does not exist, the associated tools will not load!\n")) + dllPath.c_str());
return; return;
} }
HMODULE hMod = LoadLibrary(dllPath.c_str()); HMODULE hMod = LoadLibrary(dllPath.c_str());
if (!hMod) return;
CString verStr;
verStr.Format(_T("Major ver:%d, Minor ver:%d"), ARX, SUB_ARX);
CString ver_errMsg;
ver_errMsg.Format(
CString(_T("[MCP Server]Error: Failed to load the DLL. "))
+ CString(_T("Please check whether the version of the ObjectARX SDK used by "))
+ CString("the DLL matches the current ARX version(%s).\n"), verStr );
if (!hMod)
{
AfxMessageBox(ver_errMsg);
return;
}
// 1. 定义版本校验函数指针
typedef int (*GetApiVersionFunc)();
GetApiVersionFunc getVerFn = (GetApiVersionFunc)GetProcAddress(hMod, "acrxGetApiVersion");
// 2. 核心拦截逻辑
if (!getVerFn)
{
// 连这个函数都没导出,说明根本不是合法的 ObjectARX/MCP 插件
AfxMessageBox(CString(
CString(_T("The ObjectARX version of the DLL could not be determined."))
+ CString(_T("Please ensure that the acrxGetApiVersion function is exported."))
));
FreeLibrary(hMod);
return;
}
// 拿 DLL 编译时的 SDK 版本,和基座当前运行的 SDK 版本进行比对
int arx_version(acrxGetApiVersion());
int dll_version(getVerFn());
if (arx_version != dll_version)
{
// 平台大版本不匹配,强行加载必定闪崩,果断踢掉!
FreeLibrary(hMod);
AfxMessageBox(ver_errMsg);
return;
}
typedef mcp_Tool *(*CreateFunc)(); typedef mcp_Tool *(*CreateFunc)();
typedef void (*DestroyFunc)(mcp_Tool *); typedef void (*DestroyFunc)(mcp_Tool *);
+6 -6
View File
@@ -39,7 +39,7 @@ namespace
} }
} }
#pragma region mcp_Tool_LispInline #pragma region LispInline
nlohmann::json mcp_Tool_LispInline::Execute(const nlohmann::json &args) nlohmann::json mcp_Tool_LispInline::Execute(const nlohmann::json &args)
{ {
std::string tpl = m_config["backend"].value("script_template", ""); std::string tpl = m_config["backend"].value("script_template", "");
@@ -55,9 +55,9 @@ nlohmann::json mcp_Tool_LispInline::Execute(const nlohmann::json &args)
return mcp_Tool_Utility::make_text_result("LISP inline command issued for execution (asynchronous)"); return mcp_Tool_Utility::make_text_result("LISP inline command issued for execution (asynchronous)");
} }
#pragma endregion mcp_Tool_LispInline #pragma endregion LispInline
#pragma region mcp_Tool_LispFile #pragma region LispFile
nlohmann::json mcp_Tool_LispFile::Execute(const nlohmann::json &args) nlohmann::json mcp_Tool_LispFile::Execute(const nlohmann::json &args)
{ {
fs::path filePath fs::path filePath
@@ -86,9 +86,9 @@ nlohmann::json mcp_Tool_LispFile::Execute(const nlohmann::json &args)
return mcp_Tool_Utility::make_text_result("LISP external file loaded and issued for execution (asynchronous)"); return mcp_Tool_Utility::make_text_result("LISP external file loaded and issued for execution (asynchronous)");
} }
#pragma endregion mcp_Tool_LispFile #pragma endregion LispFile
#pragma region mcp_Tool_DllProxy #pragma region DllProxy
// RAII:基座销毁工具时,自动通知 DLL 释放内存,并卸载模块 // RAII:基座销毁工具时,自动通知 DLL 释放内存,并卸载模块
mcp_Tool_DllProxy::~mcp_Tool_DllProxy() mcp_Tool_DllProxy::~mcp_Tool_DllProxy()
{ {
@@ -110,5 +110,5 @@ nlohmann::json mcp_Tool_DllProxy::Execute(const nlohmann::json &args)
} }
return mcp_Tool_Utility::make_error(-32603, "Underlying DLL tool instance is invalid"); return mcp_Tool_Utility::make_error(-32603, "Underlying DLL tool instance is invalid");
} }
#pragma endregion mcp_Tool_DllProxy #pragma endregion DllProxy
+13
View File
@@ -19,6 +19,19 @@ namespace mcp_utils
return wstrTo; return wstrTo;
} }
std::string WStringToUtf8(const std::wstring &wstr)
{
if (wstr.empty()) return std::string();
// 第一次调用获取需要的 UTF-8 缓冲区大小
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
std::string strTo(sizeNeeded, 0);
// 第二次调用真正进行转换
WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], sizeNeeded, NULL, NULL);
return strTo;
}
bool mcp_utils::hasFileExist(const std::wstring &filePath) { return _taccess(filePath.c_str(), 0) == 0; } bool mcp_utils::hasFileExist(const std::wstring &filePath) { return _taccess(filePath.c_str(), 0) == 0; }
bool mcp_utils::hasDirExist(const std::wstring &filePath) { return _taccess(filePath.c_str(), 0) == 0; } bool mcp_utils::hasDirExist(const std::wstring &filePath) { return _taccess(filePath.c_str(), 0) == 0; }
+6
View File
@@ -0,0 +1,6 @@
// StdAfx.cpp : source file that includes just the standard includes
// StdAfx.pch will be the pre-compiled header
// StdAfx.obj will contain the pre-compiled type information
#include "StdAfx.h"
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define WINVER 0x0600
#pragma warning(disable: 4786)
//- 'DEBUG workaround' below prevents the MFC or ATL #include-s from pulling
//- in "Afx.h" that would force the debug CRT through #pragma-s.
#if defined(_DEBUG) && !defined(AC_FULL_DEBUG)
#define _DEBUG_WAS_DEFINED
#undef _DEBUG
#pragma message ("Compiling MFC / STL / ATL header files in release mode.")
#endif
#pragma pack (push, 8)
#pragma warning(disable: 4786 4996)
#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#ifndef _AFX_NO_OLE_SUPPORT
#include <afxole.h> // MFC OLE classes
#include <afxodlgs.h> // MFC OLE dialog classes
#include <afxdisp.h> // MFC Automation classes
#endif
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC support for Windows Common Controls
#endif
#include <map>
#include <string>
#include <vector>
#include <afxdhtml.h>
#include <afxwin.h>
extern HINSTANCE _hdllInstance;
#ifdef _DEBUG_WAS_DEFINED
#define _DEBUG
#undef _DEBUG_WAS_DEFINED
#endif
+28
View File
@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.16)
project(cad_mcp_plugins)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(ghc_filesystem CONFIG REQUIRED)
set(SOURCES
"mcp_plugins.cpp"
"stdafx.cpp"
)
#add_def_file_name(SOURCES "${PROJECT_NAME}")
add_library(${PROJECT_NAME} SHARED ${SOURCES})
target_precompile_headers(${PROJECT_NAME} PRIVATE StdAfx.h)
target_link_objectarx(${PROJECT_NAME} AECRXBASE_EXPORT)
target_set_name_and_suffix(${PROJECT_NAME} "${PROJECT_NAME}" ".dll")
target_compile_options(${PROJECT_NAME} PRIVATE /Zc:forScope)
target_include_directories(${PROJECT_NAME} INTERFACE "${CMAKE_SOURCE_DIR}/inc")
target_link_libraries(${PROJECT_NAME} PRIVATE
nlohmann_json::nlohmann_json
)
+76
View File
@@ -0,0 +1,76 @@
#include "cadsdk.h"
#include "mcp_plugin_api.h"
#include <Windows.h>
#include "tchar.h"
#include "rxregsvc.h"
#pragma comment(lib, "rxapi.lib")
#pragma comment(linker, "/export:acrxGetApiVersion,PRIVATE")
class mcp_Tool_DrawCircleCpp : public mcp_Tool
{
public:
virtual nlohmann::json Execute(const nlohmann::json &args) override
{
// 1. 从大模型传入的参数中提取坐标和半径
double x = args.value("cx", 0.0);
double y = args.value("cy", 0.0);
double r = args.value("r", 100.0);
// 2. 获取当前活动文档
AcApDocument* pDoc = acDocManager->curDocument();
if (pDoc == nullptr)
{
return { { "error", { { "code", -32000 }, { "message", "当前没有打开的 CAD 文档" } } } };
}
// 3. 极其重要:因为调用发起者是隐式窗口消息,并非标准 CAD 命令,必须显式锁文档!
acDocManager->lockDocument(pDoc);
AcDbDatabase* pDb = pDoc->database();
AcDbBlockTable* pBlockTable = nullptr;
Acad::ErrorStatus es = pDb->getBlockTable(pBlockTable, AcDb::kForRead);
if (es == Acad::eOk)
{
AcDbBlockTableRecord* pModelSpace = nullptr;
es = pBlockTable->getAt(ACDB_MODEL_SPACE, pModelSpace, AcDb::kForWrite);
if (es == Acad::eOk)
{
// 在底层 C++ 层面构造实体对象
AcGePoint3d center(x, y, 0.0);
AcGeVector3d normal(0.0, 0.0, 1.0); // Z轴法线
AcDbCircle* pCircle = new AcDbCircle(center, normal, r);
// 追加到模型空间
AcDbObjectId circleId;
pModelSpace->appendAcDbEntity(circleId, pCircle);
// 释放对象
pCircle->close();
pModelSpace->close();
}
pBlockTable->close();
}
// 4. 解锁文档并刷新显示
acDocManager->unlockDocument(pDoc);
acedUpdateDisplay();
// 5. 返回标准 MCP 结果,如果失败可以根据 es 的值返回 make_error
if (es == Acad::eOk)
{
return mcp_Tool_Utility::make_text_result("C++底层接口已成功在模型空间生成圆形!");
}
else
{
return mcp_Tool_Utility::make_error((int)es, "ObjectARX 内部错误");
}
}
};
// 使用宏导出 C 风格工厂函数,供基座的 LoadLibrary 解析
EXPORT_MCP_TOOL(mcp_Tool_DrawCircleCpp, CreateDrawCircleToolCpp)
+40 -10
View File
@@ -1,34 +1,64 @@
import socket import socket
import json import json
import time
def send_request(req_data): def send_request(req_data, silent=False):
try: try:
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080)) client.connect(("127.0.0.1", 8080))
# 发送 UTF-8 编码的 JSON 请求
client.sendall(json.dumps(req_data).encode('utf-8')) client.sendall(json.dumps(req_data).encode('utf-8'))
# 接收并解码结果
response = client.recv(4096) response = client.recv(4096)
if not silent:
print(">>> 收到回复:") print(">>> 收到回复:")
print(json.dumps(json.loads(response.decode('utf-8')), indent=2, ensure_ascii=False)) print(json.dumps(json.loads(response.decode('utf-8')), indent=2, ensure_ascii=False))
print("-" * 50)
client.close() client.close()
except Exception as e: except Exception as e:
print(f"连接失败: {e}") print(f"连接失败: {e}")
if __name__ == "__main__": if __name__ == "__main__":
print("=== 测试 LISP 外部文件挂载 ===") print("=== 测试 1: 验证 C++ DLL 工具调用 ===")
request_call = { request_call = {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": 3, "id": 4,
"method": "tools/call", "method": "tools/call",
"params": { "params": {
"name": "draw_conference_table", "name": "draw_circle_cpp",
"arguments": { "arguments": {
"cx": 1000.0, "cx": 2000.0,
"cy": 1000.0, "cy": 2000.0,
"length": 1500.0, "r": 500.0
"width": 600.0,
"chair_count": 4,
"layer": "MCP_CONF_ROOM"
} }
} }
} }
send_request(request_call) send_request(request_call)
# 稍作停顿,方便观察 CAD 屏幕
time.sleep(1)
print("\n=== 测试 2: C++ 并发写入压力测试 (生成10个同心圆) ===")
for i in range(10):
radius = 100.0 + i * 50.0
req = {
"jsonrpc": "2.0",
"id": 100 + i,
"method": "tools/call",
"params": {
"name": "draw_circle_cpp",
"arguments": {
"cx": 4000.0,
"cy": 2000.0,
"r": radius
}
}
}
# 连续快速发送,不打印详细返回以模拟极限并发
send_request(req, silent=True)
print(">>> 并发指令发送完毕,请检查 CAD 屏幕。")