插件工程在 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。
195 lines
6.1 KiB
C++
195 lines
6.1 KiB
C++
// ============================================================================
|
||
// dwg_review_view.cpp
|
||
// 图纸审查 - 视图 / 行动层工具(均为 c++_dll 后端)
|
||
// zoom_to_entity : 缩放到指定 handle(或 handle 列表)实体
|
||
// zoom_to_window : 缩放到指定 WCS 范围(逐图框审查用;对直线组图框尤其实用)
|
||
// highlight_entities : 按 handle 列表临时高亮(改色)
|
||
// ============================================================================
|
||
|
||
#include "KTCad.h"
|
||
#include "dwg_review_common.h"
|
||
#include "mcp_plugin_api.h"
|
||
|
||
#include <string>
|
||
#include <vector>
|
||
#include <cstdio>
|
||
|
||
using namespace dwg_review;
|
||
|
||
namespace
|
||
{
|
||
// 数字格式化为命令行可用的定点字符串(避免科学计数法)
|
||
std::wstring fmt(double v)
|
||
{
|
||
wchar_t buf[64];
|
||
swprintf_s(buf, 64, L"%.6f", v);
|
||
return std::wstring(buf);
|
||
}
|
||
|
||
// 缩放到 WCS 范围(四周按 marginFrac 留边),outView 回传实际使用范围
|
||
void zoomToBox(double x1, double y1, double x2, double y2,
|
||
double marginFrac, double outView[4])
|
||
{
|
||
double w = x2 - x1;
|
||
double h = y2 - y1;
|
||
if (w < 1e-6) w = 1.0;
|
||
if (h < 1e-6) h = 1.0;
|
||
const double mx = w * marginFrac, my = h * marginFrac;
|
||
|
||
outView[0] = x1 - mx; outView[1] = y1 - my;
|
||
outView[2] = x2 + mx; outView[3] = y2 + my;
|
||
|
||
std::wstring cmd = L"_.ZOOM _W ";
|
||
cmd += fmt(outView[0]) + L"," + fmt(outView[1]) + L" ";
|
||
cmd += fmt(outView[2]) + L"," + fmt(outView[3]) + L" ";
|
||
acDocManager->sendStringToExecute(acDocManager->curDocument(), cmd.c_str());
|
||
}
|
||
|
||
// 按句柄打开实体并取包围盒
|
||
bool bboxByHandle(const std::string& hs, BBox& out)
|
||
{
|
||
AcDbObjectId id;
|
||
if (!objectIdFromHandle(hs, id)) return false;
|
||
AcDbEntity* pEnt = nullptr;
|
||
if (acdbOpenObject(pEnt, id, AcDb::kForRead) != Acad::eOk || !pEnt) return false;
|
||
const bool ok = entityBBox(pEnt, out);
|
||
pEnt->close();
|
||
return ok;
|
||
}
|
||
|
||
// 从 args 里取句柄列表(支持 "handle" 或 "handles")
|
||
std::vector<std::string> collectHandles(const nlohmann::json& args)
|
||
{
|
||
std::vector<std::string> hs;
|
||
if (args.contains("handle") && args["handle"].is_string())
|
||
hs.push_back(args["handle"].get<std::string>());
|
||
if (args.contains("handles") && args["handles"].is_array())
|
||
{
|
||
for (size_t i = 0; i < args["handles"].size(); ++i)
|
||
if (args["handles"][i].is_string())
|
||
hs.push_back(args["handles"][i].get<std::string>());
|
||
}
|
||
return hs;
|
||
}
|
||
}
|
||
|
||
// ===========================================================================
|
||
// zoom_to_entity
|
||
// ===========================================================================
|
||
class mcp_Tool_ZoomToEntity : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
const std::vector<std::string> hs = collectHandles(args);
|
||
if (hs.empty()) return makeError(kErrBadParam, "handle or handles is required");
|
||
|
||
// 合并所有目标实体的包围盒
|
||
BBox box;
|
||
bool first = true;
|
||
for (size_t i = 0; i < hs.size(); ++i)
|
||
{
|
||
BBox b;
|
||
if (!bboxByHandle(hs[i], b)) continue;
|
||
if (first) { box = b; first = false; }
|
||
else
|
||
{
|
||
if (b.minX < box.minX) box.minX = b.minX;
|
||
if (b.minY < box.minY) box.minY = b.minY;
|
||
if (b.maxX > box.maxX) box.maxX = b.maxX;
|
||
if (b.maxY > box.maxY) box.maxY = b.maxY;
|
||
}
|
||
}
|
||
if (first) return makeError(kErrNotFound, "no valid entity for the given handle(s)");
|
||
|
||
double view[4];
|
||
zoomToBox(box.minX, box.minY, box.maxX, box.maxY, 0.05, view);
|
||
|
||
nlohmann::json r;
|
||
r["ok"] = true;
|
||
r["view"] = nlohmann::json::array({ view[0], view[1], view[2], view[3] });
|
||
return makeJsonResult(r);
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_ZoomToEntity, CreateZoomToEntityTool)
|
||
|
||
// ===========================================================================
|
||
// zoom_to_window
|
||
// ===========================================================================
|
||
class mcp_Tool_ZoomToWindow : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
double x1, y1, x2, y2;
|
||
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"))
|
||
return makeError(kErrBadParam, "window requires min_x/min_y/max_x/max_y");
|
||
x1 = w["min_x"].get<double>(); y1 = w["min_y"].get<double>();
|
||
x2 = w["max_x"].get<double>(); y2 = w["max_y"].get<double>();
|
||
}
|
||
else if (args.contains("min_x") && args.contains("min_y") &&
|
||
args.contains("max_x") && args.contains("max_y"))
|
||
{
|
||
x1 = args["min_x"].get<double>(); y1 = args["min_y"].get<double>();
|
||
x2 = args["max_x"].get<double>(); y2 = args["max_y"].get<double>();
|
||
}
|
||
else
|
||
{
|
||
return makeError(kErrBadParam, "min_x/min_y/max_x/max_y (or window) is required");
|
||
}
|
||
|
||
const double margin = args.value("margin", 0.02);
|
||
|
||
double view[4];
|
||
zoomToBox(x1, y1, x2, y2, margin, view);
|
||
|
||
nlohmann::json r;
|
||
r["ok"] = true;
|
||
r["view"] = nlohmann::json::array({ view[0], view[1], view[2], view[3] });
|
||
return makeJsonResult(r);
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_ZoomToWindow, CreateZoomToWindowTool)
|
||
|
||
// ===========================================================================
|
||
// highlight_entities
|
||
// ===========================================================================
|
||
class mcp_Tool_HighlightEntities : public mcp_Tool
|
||
{
|
||
public:
|
||
nlohmann::json Execute(const nlohmann::json& args) override
|
||
{
|
||
const std::vector<std::string> hs = collectHandles(args);
|
||
if (hs.empty()) return makeError(kErrBadParam, "handle or handles is required");
|
||
|
||
// restore=true 时把颜色复位为 ByLayer(256)
|
||
const bool restore = args.value("restore", false);
|
||
const int color = restore ? 256 : args.value("color", 1); // 默认红色
|
||
|
||
int count = 0;
|
||
for (size_t i = 0; i < hs.size(); ++i)
|
||
{
|
||
AcDbObjectId id;
|
||
if (!objectIdFromHandle(hs[i], id)) continue;
|
||
|
||
AcDbEntity* pEnt = nullptr;
|
||
if (acdbOpenObject(pEnt, id, AcDb::kForWrite) != Acad::eOk || !pEnt) continue;
|
||
|
||
pEnt->setColorIndex((Adesk::UInt16)color);
|
||
pEnt->draw();
|
||
pEnt->close();
|
||
++count;
|
||
}
|
||
|
||
nlohmann::json r;
|
||
r["ok"] = true;
|
||
r["count"] = count;
|
||
return makeJsonResult(r);
|
||
}
|
||
};
|
||
EXPORT_MCP_TOOL(mcp_Tool_HighlightEntities, CreateHighlightEntitiesTool)
|