Files
cad-agent/src/cad_mcp_plugins/dwg_review_common.cpp
T
gjm bdd77af041 feat(review): 新增图纸审查工具集与逐图框审查流程
向 MCP 基座注册 15 个 C++ 工具(cad_mcp_plugins.dll):
- 概览: get_drawing_info / get_layers / get_layouts
- 数据: query_entities / get_text_content / get_dimensions /
        get_block_references / get_polylines_info / get_entity_detail
- 图框识别: detect_drawing_frames(块/闭合多段线/直线组三种形态,推算图幅)
- 检查: find_duplicate_entities / find_zero_length
- 视图: zoom_to_entity / zoom_to_window / highlight_entities

其它:
- 抽出 dwg_review_common 公共层(实体遍历 / handle / bbox / 分页 / 错误码)
- 工具返回统一为 MCP content 信封,与既有截图工具保持一致
- 新增 dwg_review_agent.py 逐图框审查脚本(deepseek-flash)
- 新增可配置审查清单 review_checklist.json
- 更新 cad-mcp-frame.json:合并视图工具与审查工具,共 18 个
- cmakelists 加入新增源文件
2026-10-08 15:21:23 +08:00

320 lines
8.5 KiB
C++

#include "cadsdk.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