现象:mcp_config.json 里配了 40 个工具,但仓库的 cad_mcp_plugins 只有 16 个工厂,
add_line / add_text / add_mtext / add_polyline / add_leader_annotation /
draw_revision_cloud / delete_entities / set_entity_properties / find_collisions /
set_drawing_area 这 10 个在 tools/list 里被静默丢掉(实测只有 30 = 18 + 12)。
来源:d:\new-start\CadProject\src\cad_mcp_plugins(pull 之后新增的
dwg_annotate_*、dwg_review_ui、find_collisions、bvh.h),整体搬到本仓库。
改动:
- 新增 bvh.h、dwg_annotate_common.cpp/h、dwg_annotate_draw.cpp、
dwg_annotate_edit.cpp、dwg_review_ui.cpp、find_collisions.cpp;
- cad_mcp_plugins/cmakelists.txt 的 SOURCES 补上这 6 个 cpp;
- 移植差异两处(都在本仓库编译/运行才暴露):
1. `#include "cadsdk.h"` -> `#include "KTCad.h"`(本仓库没有 cadsdk.h);
2. 上游 cadsdk.h 带 NOMINMAX,本仓库 KTCad.h 没有,`std::min/std::max`
被 windows.h 的宏顶掉(C2589/C2059)→ 在 dwg_annotate_common.cpp、
dwg_annotate_draw.cpp、find_collisions.cpp 里 `#undef min / #undef max`。
- 上游的写库工具没有文档锁:MCP 工具跑在主线程消息里、不在命令上下文,
kForWrite 打开块表记录直接拿到 eLockViolation(0x140),报
"cannot open target space for write"。补了 `dwg_annotate::DocLock`
(acDocManager->lockDocument/unlockDocument 的 RAII),并在
dwg_annotate_draw / dwg_annotate_edit 的 8 个 Execute 开头构造。
这一条是上游在新环境下的真 bug,不修这批工具在本仓库根本用不了。
- 所有新增文件转 UTF-8 with BOM(仓库红线)。
验证:
- R230 Debug / Z2026 Debug 编译通过并部署;dumpbin 显示
cad_mcp_plugins.dll 导出 26 个 Create*Tool + 26 个 Destroy_*。
- AutoCAD 2020 实测(部署目录放仓库的 mcp_config.json,frame 优先读它):
tools/list = **40**,配置里没有一个工具缺失;
add_line / add_text / add_polyline / add_mtext / add_leader_annotation /
draw_revision_cloud 全部返回 created[handle](图层 AI_REVIEW);
set_entity_properties 改图层+颜色、delete_entities 删两个句柄都成功;
find_collisions / find_zero_length 正常返回结构。
280 lines
7.6 KiB
C++
280 lines
7.6 KiB
C++
#include "KTCad.h"
|
||
#include "dwg_annotate_common.h"
|
||
#include "dbcolor.h" // AcCmColor
|
||
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
|
||
// windows.h(经 afxwin.h)把 min/max 定义成宏,会顶掉下面的 std::min / std::max
|
||
// (CadProject 那边的 cadsdk.h 里带了 NOMINMAX,本仓库的 KTCad.h 没有)。
|
||
#undef min
|
||
#undef max
|
||
|
||
|
||
namespace dwg_annotate
|
||
{
|
||
|
||
DocLock::DocLock()
|
||
: m_bLocked(false), m_pDoc(NULL)
|
||
{
|
||
m_pDoc = acDocManager->curDocument();
|
||
if (m_pDoc != NULL && acDocManager->lockDocument(m_pDoc) == Acad::eOk)
|
||
m_bLocked = true;
|
||
}
|
||
|
||
DocLock::~DocLock()
|
||
{
|
||
if (m_bLocked && m_pDoc != NULL)
|
||
acDocManager->unlockDocument(m_pDoc);
|
||
}
|
||
|
||
using namespace dwg_review;
|
||
|
||
const char* kDefaultAnnotLayer = "AI_REVIEW";
|
||
|
||
// ===========================================================================
|
||
// 编码
|
||
// ===========================================================================
|
||
|
||
std::wstring utf8ToWStr(const std::string& s)
|
||
{
|
||
if (s.empty()) return std::wstring();
|
||
int len = ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), NULL, 0);
|
||
if (len <= 0) return std::wstring();
|
||
std::wstring out((size_t)len, L'\0');
|
||
::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), &out[0], len);
|
||
return out;
|
||
}
|
||
|
||
// ===========================================================================
|
||
// 图层
|
||
// ===========================================================================
|
||
|
||
bool ensureLayer(AcDbDatabase* pDb, const std::string& name, int aciColor)
|
||
{
|
||
if (!pDb || name.empty()) return false;
|
||
const std::wstring wn = utf8ToWStr(name);
|
||
|
||
// 已存在则直接返回
|
||
AcDbLayerTable* pLT = nullptr;
|
||
if (pDb->getLayerTable(pLT, AcDb::kForRead) == Acad::eOk && pLT)
|
||
{
|
||
const bool exists = pLT->has(wn.c_str());
|
||
pLT->close();
|
||
if (exists) return true;
|
||
}
|
||
|
||
// 创建新图层
|
||
if (pDb->getLayerTable(pLT, AcDb::kForWrite) != Acad::eOk || !pLT) return false;
|
||
|
||
AcDbLayerTableRecord* pRec = new AcDbLayerTableRecord();
|
||
pRec->setName(wn.c_str());
|
||
AcCmColor color;
|
||
color.setColorIndex((Adesk::UInt16)aciColor);
|
||
pRec->setColor(color);
|
||
|
||
const Acad::ErrorStatus es = pLT->add(pRec);
|
||
pRec->close();
|
||
pLT->close();
|
||
return es == Acad::eOk || es == Acad::eDuplicateRecordName;
|
||
}
|
||
|
||
// ===========================================================================
|
||
// 空间
|
||
// ===========================================================================
|
||
|
||
AcDbBlockTableRecord* openWriteSpace(AcDbDatabase* pDb, const std::string& space)
|
||
{
|
||
if (!pDb) return nullptr;
|
||
|
||
AcDbBlockTable* pBT = nullptr;
|
||
if (pDb->getBlockTable(pBT, AcDb::kForRead) != Acad::eOk || !pBT) return nullptr;
|
||
|
||
AcDbBlockTableRecord* pRec = nullptr;
|
||
if (space.empty() || space == "Model" || space == "model")
|
||
{
|
||
pBT->getAt(ACDB_MODEL_SPACE, pRec, AcDb::kForWrite);
|
||
}
|
||
else
|
||
{
|
||
AcDbObjectId id;
|
||
const std::wstring ws = utf8ToWStr(space);
|
||
if (pBT->getAt(ws.c_str(), id) == Acad::eOk)
|
||
acdbOpenObject(pRec, id, AcDb::kForWrite);
|
||
}
|
||
pBT->close();
|
||
return pRec;
|
||
}
|
||
|
||
// ===========================================================================
|
||
// 参数解析
|
||
// ===========================================================================
|
||
|
||
bool parsePoint(const nlohmann::json& j, AcGePoint3d& out)
|
||
{
|
||
if (j.is_array() && j.size() >= 2)
|
||
{
|
||
out.x = j[0].get<double>();
|
||
out.y = j[1].get<double>();
|
||
out.z = (j.size() >= 3) ? j[2].get<double>() : 0.0;
|
||
return true;
|
||
}
|
||
if (j.is_object() && j.contains("x") && j.contains("y"))
|
||
{
|
||
out.x = j["x"].get<double>();
|
||
out.y = j["y"].get<double>();
|
||
out.z = j.contains("z") ? j["z"].get<double>() : 0.0;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool parsePoints(const nlohmann::json& arr, std::vector<AcGePoint3d>& out)
|
||
{
|
||
out.clear();
|
||
if (!arr.is_array()) return false;
|
||
for (size_t i = 0; i < arr.size(); ++i)
|
||
{
|
||
AcGePoint3d p;
|
||
if (!parsePoint(arr[i], p)) { out.clear(); return false; }
|
||
out.push_back(p);
|
||
}
|
||
return !out.empty();
|
||
}
|
||
|
||
bool parseWindow(const nlohmann::json& args, double& x1, double& y1, double& x2, double& 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"))
|
||
{
|
||
x1 = w["min_x"].get<double>(); y1 = w["min_y"].get<double>();
|
||
x2 = w["max_x"].get<double>(); y2 = w["max_y"].get<double>();
|
||
return true;
|
||
}
|
||
}
|
||
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>();
|
||
return true;
|
||
}
|
||
if (args.contains("p1") && args.contains("p2"))
|
||
{
|
||
AcGePoint3d a, b;
|
||
if (parsePoint(args["p1"], a) && parsePoint(args["p2"], b))
|
||
{
|
||
x1 = (std::min)(a.x, b.x); y1 = (std::min)(a.y, b.y);
|
||
x2 = (std::max)(a.x, b.x); y2 = (std::max)(a.y, b.y);
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
std::vector<std::string> collectHandlesArg(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;
|
||
}
|
||
|
||
// ===========================================================================
|
||
// 公共参数默认值
|
||
// ===========================================================================
|
||
|
||
std::string getLayerArg(const nlohmann::json& args)
|
||
{
|
||
if (args.contains("layer") && args["layer"].is_string())
|
||
return args["layer"].get<std::string>();
|
||
return std::string(kDefaultAnnotLayer);
|
||
}
|
||
|
||
int getColorArg(const nlohmann::json& args, int def)
|
||
{
|
||
if (args.contains("color") && args["color"].is_number_integer())
|
||
return args["color"].get<int>();
|
||
return def;
|
||
}
|
||
|
||
std::string getSpaceArg(const nlohmann::json& args)
|
||
{
|
||
if (args.contains("space") && args["space"].is_string())
|
||
return args["space"].get<std::string>();
|
||
return std::string("Model");
|
||
}
|
||
|
||
// ===========================================================================
|
||
// 实体写入
|
||
// ===========================================================================
|
||
|
||
void applyLayerColor(AcDbEntity* pEnt, const std::string& layer, int aciColor)
|
||
{
|
||
if (!pEnt) return;
|
||
if (!layer.empty())
|
||
{
|
||
const std::wstring wl = utf8ToWStr(layer);
|
||
pEnt->setLayer(wl.c_str());
|
||
}
|
||
pEnt->setColorIndex((Adesk::UInt16)aciColor);
|
||
}
|
||
|
||
AcDbObjectId appendToSpace(AcDbBlockTableRecord* pSpace, AcDbEntity* pEnt)
|
||
{
|
||
AcDbObjectId id = AcDbObjectId::kNull;
|
||
if (!pEnt) return id;
|
||
if (!pSpace)
|
||
{
|
||
pEnt->close();
|
||
return id;
|
||
}
|
||
if (pSpace->appendAcDbEntity(id, pEnt) != Acad::eOk)
|
||
id = AcDbObjectId::kNull;
|
||
pEnt->close();
|
||
return id;
|
||
}
|
||
|
||
std::string handleOfId(AcDbDatabase* pDb, const AcDbObjectId& id)
|
||
{
|
||
if (!pDb || id == AcDbObjectId::kNull) return std::string();
|
||
AcDbEntity* pEnt = nullptr;
|
||
if (acdbOpenObject(pEnt, id, AcDb::kForRead) != Acad::eOk || !pEnt) return std::string();
|
||
const std::string h = handleOf(pEnt);
|
||
pEnt->close();
|
||
return h;
|
||
}
|
||
|
||
// ===========================================================================
|
||
// 结果构造
|
||
// ===========================================================================
|
||
|
||
nlohmann::json makeCreatedItem(AcDbDatabase* pDb, const AcDbObjectId& id,
|
||
const std::string& type, const std::string& layer)
|
||
{
|
||
nlohmann::json item;
|
||
item["handle"] = handleOfId(pDb, id);
|
||
item["type"] = type;
|
||
item["layer"] = layer;
|
||
return item;
|
||
}
|
||
|
||
nlohmann::json createdResult(const std::vector<nlohmann::json>& created)
|
||
{
|
||
nlohmann::json r;
|
||
r["ok"] = true;
|
||
r["count"] = (int)created.size();
|
||
r["created"] = created;
|
||
return makeJsonResult(r);
|
||
}
|
||
|
||
} // namespace dwg_annotate
|