插件工程在 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。
352 lines
9.8 KiB
C++
352 lines
9.8 KiB
C++
// ============================================================================
|
|
// dwg_review_overview.cpp
|
|
// 图纸审查 - 概览层工具(均为 c++_dll 后端)
|
|
// get_drawing_info : 图纸元数据(文件名/单位/布局数/各空间实体数)
|
|
// get_layers : 图层清单(开关/冻结/锁定/颜色/线型/可选实体计数)
|
|
// get_layouts : 布局清单(模型空间 + 图纸空间布局 + 视口)
|
|
// ============================================================================
|
|
|
|
#include "KTCad.h"
|
|
#include "dwg_review_common.h"
|
|
#include "mcp_plugin_api.h"
|
|
|
|
#include "dbcolor.h" // AcCmColor(图层颜色);AcDbViewport 在 dbents.h(已由 cadsdk 引入)
|
|
|
|
#include <map>
|
|
|
|
using namespace dwg_review;
|
|
|
|
namespace
|
|
{
|
|
// 图纸单位代码 -> 名称
|
|
std::string insUnitsName(int u)
|
|
{
|
|
switch (u)
|
|
{
|
|
case 0: return "Unitless";
|
|
case 1: return "Inches";
|
|
case 2: return "Feet";
|
|
case 3: return "Miles";
|
|
case 4: return "Millimeters";
|
|
case 5: return "Centimeters";
|
|
case 6: return "Meters";
|
|
case 7: return "Kilometers";
|
|
case 8: return "Microinches";
|
|
case 9: return "Mils";
|
|
case 10: return "Yards";
|
|
case 11: return "Angstroms";
|
|
case 12: return "Nanometers";
|
|
case 13: return "Microns";
|
|
case 14: return "Decimeters";
|
|
case 15: return "Decameters";
|
|
case 16: return "Hectometers";
|
|
case 17: return "Gigameters";
|
|
case 18: return "AstronomicalUnits";
|
|
case 19: return "LightYears";
|
|
case 20: return "Parsecs";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
// 取块表记录名
|
|
std::string recordName(AcDbBlockTableRecord* pRec)
|
|
{
|
|
const ACHAR* nm = NULL;
|
|
if (pRec && pRec->getName(nm) == Acad::eOk && nm) return acharToUtf8(nm);
|
|
return std::string();
|
|
}
|
|
|
|
// 统计某空间内的实体总数
|
|
int countEntitiesIn(AcDbBlockTableRecord* pRec)
|
|
{
|
|
if (!pRec) return 0;
|
|
int n = 0;
|
|
AcDbBlockTableRecordIterator* it = nullptr;
|
|
if (pRec->newIterator(it) != Acad::eOk || !it) return 0;
|
|
for (it->start(); !it->done(); it->step()) ++n;
|
|
delete it;
|
|
return n;
|
|
}
|
|
|
|
// 取线型名
|
|
std::string linetypeName(AcDbObjectId id)
|
|
{
|
|
AcDbLinetypeTableRecord* 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();
|
|
}
|
|
|
|
// 统计各图层实体数(遍历模型空间 + 所有图纸布局)
|
|
void countByLayer(AcDbDatabase* pDb, std::map<std::string, int>& out)
|
|
{
|
|
AcDbBlockTable* pBT = nullptr;
|
|
if (pDb->getBlockTable(pBT, AcDb::kForRead) != Acad::eOk || !pBT) return;
|
|
|
|
AcDbBlockTableIterator* it = nullptr;
|
|
if (pBT->newIterator(it) == Acad::eOk && it)
|
|
{
|
|
for (it->start(); !it->done(); it->step())
|
|
{
|
|
AcDbBlockTableRecord* pRec = nullptr;
|
|
if (it->getRecord(pRec, AcDb::kForRead) != Acad::eOk || !pRec) continue;
|
|
|
|
const std::string nm = recordName(pRec);
|
|
static const std::string kModelSpace = acharToUtf8(ACDB_MODEL_SPACE);
|
|
const bool isModel = (nm == kModelSpace);
|
|
if (isModel || pRec->isLayout())
|
|
{
|
|
AcDbBlockTableRecordIterator* eit = nullptr;
|
|
if (pRec->newIterator(eit) == Acad::eOk && eit)
|
|
{
|
|
for (eit->start(); !eit->done(); eit->step())
|
|
{
|
|
AcDbEntity* pEnt = nullptr;
|
|
if (eit->getEntity(pEnt, AcDb::kForRead) == Acad::eOk && pEnt)
|
|
{
|
|
out[layerOf(pEnt)]++;
|
|
pEnt->close();
|
|
}
|
|
}
|
|
delete eit;
|
|
}
|
|
}
|
|
pRec->close();
|
|
}
|
|
delete it;
|
|
}
|
|
pBT->close();
|
|
}
|
|
|
|
// 收集某布局内的视口
|
|
nlohmann::json collectViewports(AcDbBlockTableRecord* pRec)
|
|
{
|
|
nlohmann::json arr = nlohmann::json::array();
|
|
if (!pRec) return arr;
|
|
|
|
AcDbBlockTableRecordIterator* it = nullptr;
|
|
if (pRec->newIterator(it) != Acad::eOk || !it) return arr;
|
|
|
|
for (it->start(); !it->done(); it->step())
|
|
{
|
|
AcDbEntity* pEnt = nullptr;
|
|
if (it->getEntity(pEnt, AcDb::kForRead) != Acad::eOk || !pEnt) continue;
|
|
|
|
AcDbViewport* pVp = AcDbViewport::cast(pEnt);
|
|
if (pVp)
|
|
{
|
|
nlohmann::json v;
|
|
v["handle"] = handleOf(pEnt);
|
|
v["center"] = point2Json(pVp->centerPoint().x, pVp->centerPoint().y);
|
|
v["width"] = pVp->width();
|
|
v["height"] = pVp->height();
|
|
v["scale"] = pVp->customScale();
|
|
arr.push_back(v);
|
|
}
|
|
pEnt->close();
|
|
}
|
|
delete it;
|
|
return arr;
|
|
}
|
|
}
|
|
|
|
// ===========================================================================
|
|
// get_drawing_info
|
|
// ===========================================================================
|
|
class mcp_Tool_GetDrawingInfo : public mcp_Tool
|
|
{
|
|
public:
|
|
nlohmann::json Execute(const nlohmann::json&) override
|
|
{
|
|
AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
|
|
if (!pDb) return makeError(kErrNoDocument, "no active document");
|
|
|
|
nlohmann::json r;
|
|
const ACHAR* fn = NULL;
|
|
pDb->getFilename(fn);
|
|
r["file_name"] = acharToUtf8(fn);
|
|
r["insunits"] = (int)pDb->insunits();
|
|
r["insunits_name"] = insUnitsName((int)pDb->insunits());
|
|
|
|
nlohmann::json counts = nlohmann::json::object();
|
|
int layoutCount = 0;
|
|
int total = 0;
|
|
|
|
AcDbBlockTable* pBT = nullptr;
|
|
if (pDb->getBlockTable(pBT, AcDb::kForRead) == Acad::eOk && pBT)
|
|
{
|
|
AcDbBlockTableRecord* pModel = nullptr;
|
|
if (pBT->getAt(ACDB_MODEL_SPACE, pModel, AcDb::kForRead) == Acad::eOk && pModel)
|
|
{
|
|
int c = countEntitiesIn(pModel);
|
|
counts["Model"] = c; total += c;
|
|
pModel->close();
|
|
}
|
|
|
|
AcDbBlockTableIterator* it = nullptr;
|
|
if (pBT->newIterator(it) == Acad::eOk && it)
|
|
{
|
|
for (it->start(); !it->done(); it->step())
|
|
{
|
|
AcDbBlockTableRecord* pRec = nullptr;
|
|
if (it->getRecord(pRec, AcDb::kForRead) != Acad::eOk || !pRec) continue;
|
|
if (pRec->isLayout())
|
|
{
|
|
++layoutCount;
|
|
int c = countEntitiesIn(pRec);
|
|
counts[recordName(pRec)] = c; total += c;
|
|
}
|
|
pRec->close();
|
|
}
|
|
delete it;
|
|
}
|
|
pBT->close();
|
|
}
|
|
|
|
r["layout_count"] = layoutCount + 1; // 含 Model
|
|
r["entity_count"] = counts;
|
|
r["entity_count_total"] = total;
|
|
return makeJsonResult(r);
|
|
}
|
|
};
|
|
EXPORT_MCP_TOOL(mcp_Tool_GetDrawingInfo, CreateGetDrawingInfoTool)
|
|
|
|
// ===========================================================================
|
|
// get_layers
|
|
// ===========================================================================
|
|
class mcp_Tool_GetLayers : public mcp_Tool
|
|
{
|
|
public:
|
|
nlohmann::json Execute(const nlohmann::json& args) override
|
|
{
|
|
AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
|
|
if (!pDb) return makeError(kErrNoDocument, "no active document");
|
|
|
|
const std::string nameFilter = args.value("name_filter", std::string());
|
|
const bool includeEmpty = args.value("include_empty", false);
|
|
const bool needCount = args.value("count_entities", false);
|
|
|
|
std::map<std::string, int> layerCounts;
|
|
if (needCount) countByLayer(pDb, layerCounts);
|
|
|
|
nlohmann::json items = nlohmann::json::array();
|
|
AcDbLayerTable* pLT = nullptr;
|
|
if (pDb->getLayerTable(pLT, AcDb::kForRead) == Acad::eOk && pLT)
|
|
{
|
|
AcDbLayerTableIterator* it = nullptr;
|
|
if (pLT->newIterator(it) == Acad::eOk && it)
|
|
{
|
|
for (it->start(); !it->done(); it->step())
|
|
{
|
|
AcDbLayerTableRecord* pRec = nullptr;
|
|
if (it->getRecord(pRec, AcDb::kForRead) != Acad::eOk || !pRec) continue;
|
|
|
|
const ACHAR* nm = NULL;
|
|
pRec->getName(nm);
|
|
const std::string name = acharToUtf8(nm);
|
|
|
|
const int cnt = needCount
|
|
? (layerCounts.count(name) ? layerCounts[name] : 0) : -1;
|
|
|
|
if (!wildcardMatch(name, nameFilter) ||
|
|
(needCount && !includeEmpty && cnt == 0))
|
|
{
|
|
pRec->close();
|
|
continue;
|
|
}
|
|
|
|
nlohmann::json o;
|
|
o["name"] = name;
|
|
o["on"] = !pRec->isOff();
|
|
o["frozen"] = pRec->isFrozen();
|
|
o["locked"] = pRec->isLocked();
|
|
o["plot"] = pRec->isPlottable();
|
|
|
|
AcCmColor col = pRec->color();
|
|
o["color_index"] = (int)col.colorIndex();
|
|
o["linetype"] = linetypeName(pRec->linetypeObjectId());
|
|
if (cnt >= 0) o["entity_count"] = cnt;
|
|
|
|
items.push_back(o);
|
|
pRec->close();
|
|
}
|
|
delete it;
|
|
}
|
|
pLT->close();
|
|
}
|
|
|
|
nlohmann::json r;
|
|
r["count"] = (int)items.size();
|
|
r["items"] = items;
|
|
return makeJsonResult(r);
|
|
}
|
|
};
|
|
EXPORT_MCP_TOOL(mcp_Tool_GetLayers, CreateGetLayersTool)
|
|
|
|
// ===========================================================================
|
|
// get_layouts
|
|
// ===========================================================================
|
|
class mcp_Tool_GetLayouts : public mcp_Tool
|
|
{
|
|
public:
|
|
nlohmann::json Execute(const nlohmann::json&) override
|
|
{
|
|
AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
|
|
if (!pDb) return makeError(kErrNoDocument, "no active document");
|
|
|
|
nlohmann::json items = nlohmann::json::array();
|
|
|
|
AcDbBlockTable* pBT = nullptr;
|
|
if (pDb->getBlockTable(pBT, AcDb::kForRead) == Acad::eOk && pBT)
|
|
{
|
|
// 模型空间
|
|
AcDbBlockTableRecord* pModel = nullptr;
|
|
if (pBT->getAt(ACDB_MODEL_SPACE, pModel, AcDb::kForRead) == Acad::eOk && pModel)
|
|
{
|
|
nlohmann::json o;
|
|
o["name"] = "Model";
|
|
o["is_model"] = true;
|
|
o["entity_count"] = countEntitiesIn(pModel);
|
|
o["viewports"] = nlohmann::json::array();
|
|
items.push_back(o);
|
|
pModel->close();
|
|
}
|
|
|
|
// 图纸空间布局
|
|
AcDbBlockTableIterator* it = nullptr;
|
|
if (pBT->newIterator(it) == Acad::eOk && it)
|
|
{
|
|
for (it->start(); !it->done(); it->step())
|
|
{
|
|
AcDbBlockTableRecord* pRec = nullptr;
|
|
if (it->getRecord(pRec, AcDb::kForRead) != Acad::eOk || !pRec) continue;
|
|
if (pRec->isLayout())
|
|
{
|
|
nlohmann::json o;
|
|
o["name"] = recordName(pRec);
|
|
o["is_model"] = false;
|
|
o["entity_count"] = countEntitiesIn(pRec);
|
|
o["viewports"] = collectViewports(pRec);
|
|
items.push_back(o);
|
|
}
|
|
pRec->close();
|
|
}
|
|
delete it;
|
|
}
|
|
pBT->close();
|
|
}
|
|
|
|
nlohmann::json r;
|
|
r["count"] = (int)items.size();
|
|
r["items"] = items;
|
|
return makeJsonResult(r);
|
|
}
|
|
};
|
|
EXPORT_MCP_TOOL(mcp_Tool_GetLayouts, CreateGetLayoutsTool)
|