Files
envi-code/SourceCode/Code2026/cad_mcp_plugins/dwg_review_check.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

235 lines
6.5 KiB
C++

// ============================================================================
// dwg_review_check.cpp
// 图纸审查 - 确定性检查工具(均为 c++_dll 后端)
// find_duplicate_entities : 几何重复 / 重叠实体
// find_zero_length : 零长度 / 退化实体
// ============================================================================
#include "KTCad.h"
#include "dwg_review_common.h"
#include "mcp_plugin_api.h"
#include <cmath>
#include <map>
#include <string>
#include <vector>
#include <algorithm>
using namespace dwg_review;
namespace
{
const double kPi = 3.14159265358979323846;
bool typeMatches(const std::string& type, const nlohmann::json& filter)
{
if (!filter.is_array() || filter.empty()) return true;
for (size_t i = 0; i < filter.size(); ++i)
if (filter[i].is_string() && filter[i].get<std::string>() == type) return true;
return false;
}
// 把坐标量化到 tolerance 网格,得到可比较的字符串
std::string quant(double v, double tol)
{
long long k = (long long)::llround(v / (tol > 1e-12 ? tol : 1e-12));
return std::to_string(k);
}
std::string q3(double x, double y, double z, double tol)
{
return quant(x, tol) + "," + quant(y, tol) + "," + quant(z, tol);
}
// 计算多段线总长度
double polyLength(AcDbPolyline* p)
{
if (!p) return 0.0;
double len = 0.0;
const int n = p->numVerts();
for (int i = 0; i + 1 < n; ++i)
{
AcGePoint2d a, b;
if (p->getPointAt(i, a) == Acad::eOk && p->getPointAt(i + 1, b) == Acad::eOk)
len += a.distanceTo(b);
}
if (p->isClosed() && n > 1)
{
AcGePoint2d a, b;
if (p->getPointAt(n - 1, a) == Acad::eOk && p->getPointAt(0, b) == Acad::eOk)
len += a.distanceTo(b);
}
return len;
}
// 构造几何签名(仅对支持的类型返回 true)
bool buildSignature(AcDbEntity* pEnt, double tol,
const nlohmann::json& typeFilter,
std::string& sig, std::string& type)
{
type = entityTypeName(pEnt);
if (!typeMatches(type, typeFilter)) return false;
if (AcDbLine* p = AcDbLine::cast(pEnt))
{
double ax = p->startPoint().x, ay = p->startPoint().y, az = p->startPoint().z;
double bx = p->endPoint().x, by = p->endPoint().y, bz = p->endPoint().z;
// 端点顺序无关
if (ax > bx || (ax == bx && ay > by) || (ax == bx && ay == by && az > bz))
{
std::swap(ax, bx); std::swap(ay, by); std::swap(az, bz);
}
sig = "LINE:" + q3(ax, ay, az, tol) + ";" + q3(bx, by, bz, tol);
return true;
}
if (AcDbArc* p = AcDbArc::cast(pEnt))
{
sig = "ARC:" + q3(p->center().x, p->center().y, p->center().z, tol)
+ ";r" + quant(p->radius(), tol)
+ ";a" + quant(p->startAngle(), tol) + ";" + quant(p->endAngle(), tol);
return true;
}
if (AcDbCircle* p = AcDbCircle::cast(pEnt))
{
sig = "CIRCLE:" + q3(p->center().x, p->center().y, p->center().z, tol)
+ ";r" + quant(p->radius(), tol);
return true;
}
if (AcDbPoint* p = AcDbPoint::cast(pEnt))
{
sig = "POINT:" + q3(p->position().x, p->position().y, p->position().z, tol);
return true;
}
if (AcDbPolyline* p = AcDbPolyline::cast(pEnt))
{
std::string s = "LWPOLYLINE:";
const int n = p->numVerts();
for (int i = 0; i < n; ++i)
{
AcGePoint2d pt;
if (p->getPointAt(i, pt) != Acad::eOk) return false;
s += q3(pt.x, pt.y, 0.0, tol) + "|";
}
s += (p->isClosed() ? "C" : "O");
sig = s;
return true;
}
return false;
}
}
// ===========================================================================
// find_duplicate_entities
// ===========================================================================
class mcp_Tool_FindDuplicateEntities : public mcp_Tool
{
public:
nlohmann::json Execute(const nlohmann::json& args) override
{
const CommonQuery q = parseCommonQuery(args);
const double tol = args.value("tolerance", 0.001);
const nlohmann::json typeFilter =
args.contains("type_filter") ? args["type_filter"] : nlohmann::json();
const int groupLimit = args.value("limit", 200);
std::map<std::string, std::vector<std::string> > buckets;
std::map<std::string, std::string> bucketType;
forEachEntity(q, [&](AcDbEntity* pEnt) -> bool
{
std::string sig, type;
if (buildSignature(pEnt, tol, typeFilter, sig, type))
{
buckets[sig].push_back(handleOf(pEnt));
bucketType[sig] = type;
}
return true;
});
nlohmann::json groups = nlohmann::json::array();
int count = 0;
for (std::map<std::string, std::vector<std::string> >::iterator it = buckets.begin();
it != buckets.end(); ++it)
{
if (it->second.size() < 2) continue; // 只保留重复组
if (count >= groupLimit) break;
nlohmann::json g;
g["reason"] = "identical_geometry";
g["type"] = bucketType[it->first];
nlohmann::json hs = nlohmann::json::array();
for (size_t i = 0; i < it->second.size(); ++i) hs.push_back(it->second[i]);
g["handles"] = hs;
groups.push_back(g);
++count;
}
nlohmann::json r;
r["count"] = count;
r["groups"] = groups;
return makeJsonResult(r);
}
};
EXPORT_MCP_TOOL(mcp_Tool_FindDuplicateEntities, CreateFindDuplicateEntitiesTool)
// ===========================================================================
// find_zero_length
// ===========================================================================
class mcp_Tool_FindZeroLength : public mcp_Tool
{
public:
nlohmann::json Execute(const nlohmann::json& args) override
{
const CommonQuery q = parseCommonQuery(args);
const double tol = args.value("tolerance", 0.001);
int total = 0, returned = 0;
nlohmann::json items = nlohmann::json::array();
forEachEntity(q, [&](AcDbEntity* pEnt) -> bool
{
double len = -1.0;
const std::string type = entityTypeName(pEnt);
if (AcDbLine* p = AcDbLine::cast(pEnt))
{
len = p->startPoint().distanceTo(p->endPoint());
}
else if (AcDbArc* p = AcDbArc::cast(pEnt))
{
double span = p->endAngle() - p->startAngle();
while (span < 0.0) span += 2.0 * kPi;
while (span > 2.0 * kPi) span -= 2.0 * kPi;
len = p->radius() * span;
}
else if (AcDbPolyline* p = AcDbPolyline::cast(pEnt))
{
len = polyLength(p);
}
else
{
return true;
}
if (len >= 0.0 && len < tol)
{
if (total >= q.offset && returned < q.limit)
{
nlohmann::json o;
o["handle"] = handleOf(pEnt);
o["type"] = type;
o["layer"] = layerOf(pEnt);
o["length"] = len;
items.push_back(o);
++returned;
}
++total;
}
return true;
});
return makeJsonResult(pageResult(total, returned, q.offset, items));
}
};
EXPORT_MCP_TOOL(mcp_Tool_FindZeroLength, CreateFindZeroLengthTool)