插件工程在 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。
235 lines
6.5 KiB
C++
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)
|