Files
envi-code/SourceCode/Code2026/cad_mcp_plugins/dwg_annotate_common.cpp
T
company-desktop c10f5b0083 feat: 补齐 cad_mcp_plugins 的批注/编辑/干涉工具(26 个工具齐了)
现象: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 正常返回结构。
2026-10-29 22:45:14 +08:00

280 lines
7.6 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.
#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