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

441 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================================
// 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)