插件工程在 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。
320 lines
8.5 KiB
C++
320 lines
8.5 KiB
C++
#include "KTCad.h"
|
|
#include "dwg_review_common.h"
|
|
#include "mcp_plugin_api.h" // 复用 mcp_Tool_Utility 构造 MCP 结果信封
|
|
|
|
#include <cctype>
|
|
#include <cmath>
|
|
|
|
namespace dwg_review
|
|
{
|
|
|
|
// ===========================================================================
|
|
// 字符串 / 实体基础读取
|
|
// ===========================================================================
|
|
|
|
std::string wstrToUtf8(const std::wstring& w)
|
|
{
|
|
if (w.empty()) return std::string();
|
|
int len = ::WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), NULL, 0, NULL, NULL);
|
|
if (len <= 0) return std::string();
|
|
std::string out((size_t)len, '\0');
|
|
::WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), &out[0], len, NULL, NULL);
|
|
return out;
|
|
}
|
|
|
|
std::string acharToUtf8(const ACHAR* s)
|
|
{
|
|
if (!s) return std::string();
|
|
#ifdef _UNICODE
|
|
return wstrToUtf8(std::wstring(s));
|
|
#else
|
|
return std::string(s);
|
|
#endif
|
|
}
|
|
|
|
std::string handleOf(AcDbEntity* pEnt)
|
|
{
|
|
if (!pEnt) return std::string();
|
|
AcDbHandle h;
|
|
pEnt->getAcDbHandle(h);
|
|
ACHAR buf[64] = { 0 };
|
|
h.getIntoAsciiBuffer(buf, 64);
|
|
return acharToUtf8(buf);
|
|
}
|
|
|
|
std::string layerOf(AcDbEntity* pEnt)
|
|
{
|
|
if (!pEnt) return std::string();
|
|
return acharToUtf8(pEnt->layer());
|
|
}
|
|
|
|
std::string entityTypeName(AcDbEntity* pEnt)
|
|
{
|
|
if (!pEnt) return std::string();
|
|
|
|
// 注意判定顺序:派生类必须先于基类
|
|
if (AcDbBlockReference::cast(pEnt)) return "INSERT";
|
|
if (AcDbAttribute::cast(pEnt)) return "ATTRIB";
|
|
if (AcDbAttributeDefinition::cast(pEnt)) return "ATTDEF";
|
|
if (AcDbMText::cast(pEnt)) return "MTEXT";
|
|
if (AcDbText::cast(pEnt)) return "TEXT";
|
|
if (AcDbArc::cast(pEnt)) return "ARC"; // Arc 派生自 Circle
|
|
if (AcDbCircle::cast(pEnt)) return "CIRCLE";
|
|
if (AcDbLine::cast(pEnt)) return "LINE";
|
|
if (AcDbPolyline::cast(pEnt)) return "LWPOLYLINE";
|
|
if (AcDb2dPolyline::cast(pEnt)) return "POLYLINE";
|
|
if (AcDbDimension::cast(pEnt)) return "DIMENSION";
|
|
if (AcDbEllipse::cast(pEnt)) return "ELLIPSE";
|
|
if (AcDbSpline::cast(pEnt)) return "SPLINE";
|
|
if (AcDbPoint::cast(pEnt)) return "POINT";
|
|
|
|
// 兜底:取类名去掉 AcDb 前缀并大写
|
|
std::string n = acharToUtf8(pEnt->isA()->name());
|
|
if (n.rfind("AcDb", 0) == 0) n = n.substr(4);
|
|
for (size_t i = 0; i < n.size(); ++i)
|
|
n[i] = (char)::toupper((unsigned char)n[i]);
|
|
return n;
|
|
}
|
|
|
|
bool entityBBox(AcDbEntity* pEnt, BBox& out)
|
|
{
|
|
out = BBox();
|
|
if (!pEnt) return false;
|
|
try
|
|
{
|
|
AcDbExtents ext;
|
|
if (pEnt->getGeomExtents(ext) != Acad::eOk) return false;
|
|
out.minX = ext.minPoint().x;
|
|
out.minY = ext.minPoint().y;
|
|
out.maxX = ext.maxPoint().x;
|
|
out.maxY = ext.maxPoint().y;
|
|
out.valid = true;
|
|
return true;
|
|
}
|
|
catch (...)
|
|
{
|
|
return false; // 部分实体(如空块、特殊动态块)取范围会抛异常
|
|
}
|
|
}
|
|
|
|
std::string blockNameOf(AcDbBlockReference* pRef)
|
|
{
|
|
if (!pRef) return std::string();
|
|
AcDbBlockTableRecord* pRec = nullptr;
|
|
std::string name;
|
|
if (acdbOpenObject(pRec, pRef->blockTableRecord(), AcDb::kForRead) == Acad::eOk && pRec)
|
|
{
|
|
const ACHAR* nm = NULL;
|
|
if (pRec->getName(nm) == Acad::eOk && nm) name = acharToUtf8(nm);
|
|
pRec->close();
|
|
}
|
|
return name;
|
|
}
|
|
|
|
bool objectIdFromHandle(const std::string& handleStr, AcDbObjectId& out)
|
|
{
|
|
AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
|
|
if (!pDb) return false;
|
|
std::wstring ws(handleStr.begin(), handleStr.end());
|
|
AcDbHandle h(ws.c_str()); // AcDbHandle(const ACHAR*)
|
|
return pDb->getAcDbObjectId(out, false, h) == Acad::eOk;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// 匹配 / 过滤
|
|
// ===========================================================================
|
|
|
|
bool wildcardMatch(const std::string& name, const std::string& pattern)
|
|
{
|
|
if (pattern.empty()) return true; // 空模式 = 全部匹配
|
|
|
|
size_t n = name.size(), m = pattern.size();
|
|
size_t i = 0, j = 0, star = (size_t)-1, mark = 0;
|
|
while (i < n)
|
|
{
|
|
if (j < m && (pattern[j] == '?' ||
|
|
::tolower((unsigned char)pattern[j]) == ::tolower((unsigned char)name[i])))
|
|
{
|
|
++i; ++j;
|
|
}
|
|
else if (j < m && pattern[j] == '*')
|
|
{
|
|
star = j++; mark = i;
|
|
}
|
|
else if (star != (size_t)-1)
|
|
{
|
|
j = star + 1; i = ++mark;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
while (j < m && pattern[j] == '*') ++j;
|
|
return j == m;
|
|
}
|
|
|
|
bool bboxInWindow(const BBox& b, const CommonQuery& q)
|
|
{
|
|
if (!q.useWindow) return true;
|
|
if (!b.valid) return false;
|
|
if (b.maxX < q.minX || b.minX > q.maxX) return false;
|
|
if (b.maxY < q.minY || b.minY > q.maxY) return false;
|
|
return true;
|
|
}
|
|
|
|
bool layerAccepted(AcDbEntity* pEnt, const CommonQuery& q)
|
|
{
|
|
return wildcardMatch(layerOf(pEnt), q.layerFilter);
|
|
}
|
|
|
|
// ===========================================================================
|
|
// 空间定位 / 遍历
|
|
// ===========================================================================
|
|
|
|
AcDbBlockTableRecord* openSpace(AcDbDatabase* pDb, const std::string& space)
|
|
{
|
|
if (!pDb) return nullptr;
|
|
|
|
AcDbBlockTable* pBT = nullptr;
|
|
if (pDb->getBlockTable(pBT, AcDb::kForRead) != Acad::eOk) return nullptr;
|
|
|
|
AcDbBlockTableRecord* pRec = nullptr;
|
|
if (space.empty() || space == "Model" || space == "model")
|
|
{
|
|
pBT->getAt(ACDB_MODEL_SPACE, pRec, AcDb::kForRead);
|
|
}
|
|
else
|
|
{
|
|
AcDbObjectId id;
|
|
std::wstring ws(space.begin(), space.end());
|
|
if (pBT->getAt(ws.c_str(), id) == Acad::eOk)
|
|
acdbOpenObject(pRec, id, AcDb::kForRead);
|
|
}
|
|
pBT->close();
|
|
return pRec;
|
|
}
|
|
|
|
void forEachEntity(const CommonQuery& q, const EntityVisitor& visitor)
|
|
{
|
|
AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
|
|
if (!pDb) return;
|
|
|
|
AcDbBlockTableRecord* pSpace = openSpace(pDb, q.space);
|
|
if (!pSpace) return;
|
|
|
|
AcDbBlockTableRecordIterator* pIter = nullptr;
|
|
if (pSpace->newIterator(pIter) != Acad::eOk || !pIter)
|
|
{
|
|
pSpace->close();
|
|
return;
|
|
}
|
|
|
|
for (pIter->start(); !pIter->done(); pIter->step())
|
|
{
|
|
AcDbEntity* pEnt = nullptr;
|
|
if (pIter->getEntity(pEnt, AcDb::kForRead) != Acad::eOk || !pEnt) continue;
|
|
|
|
bool cont = true;
|
|
if (layerAccepted(pEnt, q))
|
|
{
|
|
if (q.useWindow)
|
|
{
|
|
BBox b;
|
|
if (entityBBox(pEnt, b) && bboxInWindow(b, q))
|
|
cont = visitor(pEnt);
|
|
}
|
|
else
|
|
{
|
|
cont = visitor(pEnt);
|
|
}
|
|
}
|
|
pEnt->close();
|
|
if (!cont) break;
|
|
}
|
|
|
|
delete pIter;
|
|
pSpace->close();
|
|
}
|
|
|
|
// ===========================================================================
|
|
// 通用查询参数解析
|
|
// ===========================================================================
|
|
|
|
CommonQuery parseCommonQuery(const nlohmann::json& args)
|
|
{
|
|
CommonQuery q;
|
|
|
|
if (args.contains("space") && args["space"].is_string())
|
|
q.space = args["space"].get<std::string>();
|
|
if (args.contains("layer_filter") && args["layer_filter"].is_string())
|
|
q.layerFilter = args["layer_filter"].get<std::string>();
|
|
if (args.contains("limit") && args["limit"].is_number_integer())
|
|
q.limit = args["limit"].get<int>();
|
|
if (args.contains("offset") && args["offset"].is_number_integer())
|
|
q.offset = args["offset"].get<int>();
|
|
|
|
if (q.limit <= 0) q.limit = 200;
|
|
if (q.limit > 1000) q.limit = 1000;
|
|
if (q.offset < 0) q.offset = 0;
|
|
|
|
if (args.contains("window") && args["window"].is_object())
|
|
{
|
|
const nlohmann::json& w = args["window"];
|
|
if (w.contains("min_x") && w.contains("min_y") &&
|
|
w.contains("max_x") && w.contains("max_y"))
|
|
{
|
|
q.useWindow = true;
|
|
q.minX = w["min_x"].get<double>();
|
|
q.minY = w["min_y"].get<double>();
|
|
q.maxX = w["max_x"].get<double>();
|
|
q.maxY = w["max_y"].get<double>();
|
|
}
|
|
}
|
|
return q;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// 结果封装
|
|
// ===========================================================================
|
|
|
|
nlohmann::json pageResult(int total, int returned, int offset, const nlohmann::json& items)
|
|
{
|
|
nlohmann::json j;
|
|
j["total"] = total;
|
|
j["returned"] = returned;
|
|
j["offset"] = offset;
|
|
j["items"] = items;
|
|
return j;
|
|
}
|
|
|
|
nlohmann::json makeError(int code, const std::string& msg)
|
|
{
|
|
nlohmann::json j;
|
|
j["error"] = { { "code", code }, { "message", msg } };
|
|
return j;
|
|
}
|
|
|
|
nlohmann::json makeJsonResult(const nlohmann::json& data)
|
|
{
|
|
std::wstring w = mcp_Tool_Utility::utf8ToWString(data.dump());
|
|
return mcp_Tool_Utility::make_text_result(w);
|
|
}
|
|
|
|
nlohmann::json point2Json(double x, double y)
|
|
{
|
|
return nlohmann::json::array({ x, y });
|
|
}
|
|
|
|
nlohmann::json point3Json(double x, double y, double z)
|
|
{
|
|
return nlohmann::json::array({ x, y, z });
|
|
}
|
|
|
|
nlohmann::json bboxJson(const BBox& b)
|
|
{
|
|
return nlohmann::json::array({ b.minX, b.minY, b.maxX, b.maxY });
|
|
}
|
|
|
|
} // namespace dwg_review
|