插件工程在 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。
441 lines
13 KiB
C++
441 lines
13 KiB
C++
// ============================================================================
|
||
// dwg_review_query.cpp
|
||
// 图纸审查 - 数据提取层工具(均为 c++_dll 后端,统一分页返回)
|
||
// query_entities : 按图层/类型/范围查询图元摘要
|
||
// get_text_content : 提取 TEXT / MTEXT / ATTRIB 文字
|
||
// get_dimensions : 提取标注
|
||
// get_block_references : 提取块参照及其属性
|
||
// get_polylines_info : 多段线闭合性 / 面积 / 长度
|
||
// get_entity_detail : 按 handle 取单个实体完整属性
|
||
//
|
||
// 返回统一分页信封:{ total, returned, offset, items:[...] }
|
||
// 实体 ID 统一使用 handle。
|
||
// ============================================================================
|
||
|
||
#include "KTCad.h"
|
||
#include "dwg_review_common.h"
|
||
#include "mcp_plugin_api.h"
|
||
|
||
#include "dbdim.h" // AcDbDimension(cadsdk 未包含)
|
||
|
||
#include <string>
|
||
|
||
using namespace dwg_review;
|
||
|
||
namespace
|
||
{
|
||
// 类型过滤:filter 为空数组/非数组时全部通过
|
||
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;
|
||
}
|
||
|
||
// 取类名(去掉 AcDb 前缀),如 "RotatedDimension"
|
||
std::string rawClassName(AcRxObject* pObj)
|
||
{
|
||
if (!pObj) return std::string();
|
||
std::string n = acharToUtf8(pObj->isA()->name());
|
||
if (n.rfind("AcDb", 0) == 0) n = n.substr(4);
|
||
return n;
|
||
}
|
||
|
||
// 取标注样式名
|
||
std::string dimStyleName(AcDbObjectId id)
|
||
{
|
||
AcDbDimStyleTableRecord* pRec = nullptr;
|
||
if (acdbOpenObject(pRec, id, AcDb::kForRead) == Acad::eOk && pRec)
|
||
{
|
||
const ACHAR* nm = NULL;
|
||
std::string s;
|
||
if (pRec->getName(nm) == Acad::eOk && nm) s = acharToUtf8(nm);
|
||
pRec->close();
|
||
return s;
|
||
}
|
||
return std::string();
|
||
}
|
||
|
||
// 计算多段线总长度
|
||
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;
|
||
}
|
||
|
||
// 通用摘要:handle / type / layer / bbox + 类型相关 properties
|
||
nlohmann::json buildEntitySummary(AcDbEntity* pEnt)
|
||
{
|
||
nlohmann::json o;
|
||
o["handle"] = handleOf(pEnt);
|
||
o["type"] = entityTypeName(pEnt);
|
||
o["layer"] = layerOf(pEnt);
|
||
|
||
BBox b;
|
||
if (entityBBox(pEnt, b)) o["bbox"] = bboxJson(b);
|
||
|
||
nlohmann::json props = nlohmann::json::object();
|
||
|
||
if (AcDbLine* p = AcDbLine::cast(pEnt))
|
||
{
|
||
props["start"] = point3Json(p->startPoint().x, p->startPoint().y, p->startPoint().z);
|
||
props["end"] = point3Json(p->endPoint().x, p->endPoint().y, p->endPoint().z);
|
||
}
|
||
else if (AcDbArc* p = AcDbArc::cast(pEnt))
|
||
{
|
||
props["center"] = point3Json(p->center().x, p->center().y, p->center().z);
|
||
props["radius"] = p->radius();
|
||
props["start_angle"] = p->startAngle();
|
||
props["end_angle"] = p->endAngle();
|
||
}
|
||
else if (AcDbCircle* p = AcDbCircle::cast(pEnt))
|
||
{
|
||
props["center"] = point3Json(p->center().x, p->center().y, p->center().z);
|
||
props["radius"] = p->radius();
|
||
}
|
||
else if (AcDbPolyline* p = AcDbPolyline::cast(pEnt))
|
||
{
|
||
props["vertex_count"] = p->numVerts();
|
||
props["closed"] = p->isClosed();
|
||
}
|
||
else if (AcDbPoint* p = AcDbPoint::cast(pEnt))
|
||
{
|
||
props["position"] = point3Json(p->position().x, p->position().y, p->position().z);
|
||
}
|
||
else if (AcDbBlockReference* p = AcDbBlockReference::cast(pEnt))
|
||
{
|
||
props["block_name"] = blockNameOf(p);
|
||
}
|
||
|
||
o["properties"] = props;
|
||
return o;
|
||
}
|
||
}
|
||
|
||
// ===========================================================================
|
||
// query_entities
|
||
// ===========================================================================
|
||
class mcp_Tool_QueryEntities : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
const CommonQuery q = parseCommonQuery(args);
|
||
const nlohmann::json typeFilter =
|
||
args.contains("type_filter") ? args["type_filter"] : nlohmann::json();
|
||
|
||
int total = 0, returned = 0;
|
||
nlohmann::json items = nlohmann::json::array();
|
||
|
||
forEachEntity(q, [&](AcDbEntity* pEnt) -> bool
|
||
{
|
||
if (!typeMatches(entityTypeName(pEnt), typeFilter)) return true;
|
||
if (total >= q.offset && returned < q.limit)
|
||
{
|
||
items.push_back(buildEntitySummary(pEnt));
|
||
++returned;
|
||
}
|
||
++total;
|
||
return true;
|
||
});
|
||
|
||
return makeJsonResult(pageResult(total, returned, q.offset, items));
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_QueryEntities, CreateQueryEntitiesTool)
|
||
|
||
// ===========================================================================
|
||
// get_text_content
|
||
// ===========================================================================
|
||
class mcp_Tool_GetTextContent : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
const CommonQuery q = parseCommonQuery(args);
|
||
const std::string contentFilter = args.value("content_filter", std::string());
|
||
|
||
int total = 0, returned = 0;
|
||
nlohmann::json items = nlohmann::json::array();
|
||
|
||
forEachEntity(q, [&](AcDbEntity* pEnt) -> bool
|
||
{
|
||
const std::string type = entityTypeName(pEnt);
|
||
if (type != "TEXT" && type != "MTEXT" && type != "ATTRIB") return true;
|
||
|
||
std::string content;
|
||
double height = 0.0;
|
||
nlohmann::json o;
|
||
o["handle"] = handleOf(pEnt);
|
||
o["type"] = type;
|
||
o["layer"] = layerOf(pEnt);
|
||
|
||
if (AcDbMText* p = AcDbMText::cast(pEnt))
|
||
{
|
||
content = acharToUtf8(p->contents());
|
||
height = p->textHeight();
|
||
o["insert"] = point3Json(p->location().x, p->location().y, p->location().z);
|
||
}
|
||
else if (AcDbAttribute* p = AcDbAttribute::cast(pEnt))
|
||
{
|
||
content = acharToUtf8(p->textString());
|
||
height = p->height();
|
||
o["insert"] = point3Json(p->position().x, p->position().y, p->position().z);
|
||
}
|
||
else if (AcDbText* p = AcDbText::cast(pEnt))
|
||
{
|
||
content = acharToUtf8(p->textString());
|
||
height = p->height();
|
||
o["rotation"] = p->rotation();
|
||
o["insert"] = point3Json(p->position().x, p->position().y, p->position().z);
|
||
}
|
||
else
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// 可选内容过滤(子串,大小写敏感)
|
||
if (!contentFilter.empty() && content.find(contentFilter) == std::string::npos)
|
||
return true;
|
||
|
||
o["content"] = content;
|
||
o["height"] = height;
|
||
BBox b;
|
||
if (entityBBox(pEnt, b)) o["bbox"] = bboxJson(b);
|
||
|
||
if (total >= q.offset && returned < q.limit)
|
||
{
|
||
items.push_back(o);
|
||
++returned;
|
||
}
|
||
++total;
|
||
return true;
|
||
});
|
||
|
||
return makeJsonResult(pageResult(total, returned, q.offset, items));
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_GetTextContent, CreateGetTextContentTool)
|
||
|
||
// ===========================================================================
|
||
// get_dimensions
|
||
// ===========================================================================
|
||
class mcp_Tool_GetDimensions : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
const CommonQuery q = parseCommonQuery(args);
|
||
|
||
int total = 0, returned = 0;
|
||
nlohmann::json items = nlohmann::json::array();
|
||
|
||
forEachEntity(q, [&](AcDbEntity* pEnt) -> bool
|
||
{
|
||
AcDbDimension* pDim = AcDbDimension::cast(pEnt);
|
||
if (!pDim) return true;
|
||
|
||
if (total >= q.offset && returned < q.limit)
|
||
{
|
||
nlohmann::json o;
|
||
o["handle"] = handleOf(pEnt);
|
||
o["type"] = rawClassName(pDim);
|
||
o["layer"] = layerOf(pEnt);
|
||
double meas = 0.0;
|
||
pDim->measurement(meas);
|
||
o["measurement"] = meas;
|
||
o["text_override"] = acharToUtf8(pDim->dimensionText());
|
||
o["dim_style"] = dimStyleName(pDim->dimensionStyle());
|
||
items.push_back(o);
|
||
++returned;
|
||
}
|
||
++total;
|
||
return true;
|
||
});
|
||
|
||
return makeJsonResult(pageResult(total, returned, q.offset, items));
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_GetDimensions, CreateGetDimensionsTool)
|
||
|
||
// ===========================================================================
|
||
// get_block_references
|
||
// ===========================================================================
|
||
class mcp_Tool_GetBlockReferences : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
const CommonQuery q = parseCommonQuery(args);
|
||
const std::string blockFilter = args.value("block_name_filter", std::string());
|
||
|
||
int total = 0, returned = 0;
|
||
nlohmann::json items = nlohmann::json::array();
|
||
|
||
forEachEntity(q, [&](AcDbEntity* pEnt) -> bool
|
||
{
|
||
AcDbBlockReference* pRef = AcDbBlockReference::cast(pEnt);
|
||
if (!pRef) return true;
|
||
|
||
const std::string blk = blockNameOf(pRef);
|
||
if (!blockFilter.empty() && !wildcardMatch(blk, blockFilter)) return true;
|
||
|
||
if (total >= q.offset && returned < q.limit)
|
||
{
|
||
nlohmann::json o;
|
||
o["handle"] = handleOf(pEnt);
|
||
o["type"] = "INSERT";
|
||
o["layer"] = layerOf(pEnt);
|
||
o["block_name"] = blk;
|
||
o["insert"] = point3Json(pRef->position().x, pRef->position().y, pRef->position().z);
|
||
o["rotation"] = pRef->rotation();
|
||
AcGeScale3d sc = pRef->scaleFactors();
|
||
o["scale"] = nlohmann::json::array({ sc.sx, sc.sy, sc.sz });
|
||
|
||
// 属性
|
||
nlohmann::json atts = nlohmann::json::array();
|
||
AcDbObjectIterator* ait = pRef->attributeIterator();
|
||
if (ait)
|
||
{
|
||
for (; !ait->done(); ait->step())
|
||
{
|
||
AcDbAttribute* pAtt = nullptr;
|
||
if (acdbOpenObject(pAtt, ait->objectId(), AcDb::kForRead) == Acad::eOk && pAtt)
|
||
{
|
||
nlohmann::json a;
|
||
a["tag"] = acharToUtf8(pAtt->tag());
|
||
a["value"] = acharToUtf8(pAtt->textString());
|
||
atts.push_back(a);
|
||
pAtt->close();
|
||
}
|
||
}
|
||
delete ait;
|
||
}
|
||
o["attributes"] = atts;
|
||
|
||
BBox b;
|
||
if (entityBBox(pEnt, b)) o["bbox"] = bboxJson(b);
|
||
|
||
items.push_back(o);
|
||
++returned;
|
||
}
|
||
++total;
|
||
return true;
|
||
});
|
||
|
||
return makeJsonResult(pageResult(total, returned, q.offset, items));
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_GetBlockReferences, CreateGetBlockReferencesTool)
|
||
|
||
// ===========================================================================
|
||
// get_polylines_info
|
||
// ===========================================================================
|
||
class mcp_Tool_GetPolylinesInfo : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
const CommonQuery q = parseCommonQuery(args);
|
||
|
||
int total = 0, returned = 0;
|
||
nlohmann::json items = nlohmann::json::array();
|
||
|
||
forEachEntity(q, [&](AcDbEntity* pEnt) -> bool
|
||
{
|
||
AcDbPolyline* p = AcDbPolyline::cast(pEnt);
|
||
if (!p) return true;
|
||
|
||
if (total >= q.offset && returned < q.limit)
|
||
{
|
||
nlohmann::json o;
|
||
o["handle"] = handleOf(pEnt);
|
||
o["type"] = "LWPOLYLINE";
|
||
o["layer"] = layerOf(pEnt);
|
||
o["vertex_count"] = p->numVerts();
|
||
o["closed"] = p->isClosed();
|
||
o["length"] = polyLength(p);
|
||
double area = 0.0;
|
||
if (p->getArea(area) == Acad::eOk) o["area"] = area;
|
||
BBox b;
|
||
if (entityBBox(pEnt, b)) o["bbox"] = bboxJson(b);
|
||
items.push_back(o);
|
||
++returned;
|
||
}
|
||
++total;
|
||
return true;
|
||
});
|
||
|
||
return makeJsonResult(pageResult(total, returned, q.offset, items));
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_GetPolylinesInfo, CreateGetPolylinesInfoTool)
|
||
|
||
// ===========================================================================
|
||
// get_entity_detail
|
||
// ===========================================================================
|
||
class mcp_Tool_GetEntityDetail : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
if (!args.contains("handle") || !args["handle"].is_string())
|
||
return makeError(kErrBadParam, "handle is required");
|
||
|
||
const std::string hs = args["handle"].get<std::string>();
|
||
|
||
AcDbObjectId id;
|
||
if (!objectIdFromHandle(hs, id))
|
||
return makeError(kErrNotFound, "invalid handle: " + hs);
|
||
|
||
AcDbEntity* pEnt = nullptr;
|
||
if (acdbOpenObject(pEnt, id, AcDb::kForRead) != Acad::eOk || !pEnt)
|
||
return makeError(kErrNotFound, "entity not found: " + hs);
|
||
|
||
nlohmann::json o = buildEntitySummary(pEnt);
|
||
|
||
// 补充文字 / 标注 / 块 等细节
|
||
if (AcDbText* p = AcDbText::cast(pEnt))
|
||
{
|
||
o["text"] = acharToUtf8(p->textString());
|
||
o["height"] = p->height();
|
||
o["rotation"] = p->rotation();
|
||
}
|
||
else if (AcDbMText* p = AcDbMText::cast(pEnt))
|
||
{
|
||
o["text"] = acharToUtf8(p->contents());
|
||
o["height"] = p->textHeight();
|
||
}
|
||
else if (AcDbDimension* p = AcDbDimension::cast(pEnt))
|
||
{
|
||
double meas = 0.0;
|
||
p->measurement(meas);
|
||
o["measurement"] = meas;
|
||
o["text_override"] = acharToUtf8(p->dimensionText());
|
||
o["dim_style"] = dimStyleName(p->dimensionStyle());
|
||
}
|
||
|
||
pEnt->close();
|
||
return makeJsonResult(o);
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_GetEntityDetail, CreateGetEntityDetailTool)
|