Files
envi-code/SourceCode/Code2026/cad_mcp_aec/aec_mcp_common.cpp
T
company-desktop 132615af42 feat: 新增 cad_mcp_aec 插件,让 agent 直接建/改 Envi3D 自定义实体
现象:cad_mcp_frame 的插件机制只能挂"纯 AcDb 层"的只读工具
(cad_mcp_plugins 不链接 CoreCommand,拿不到 AecDbEwPipe / AecDbXWall /
AecDbXSlab / AecDbColumn / AecDbBeam 这些类的实现),agent 没法改仓库的自定义实体。

改动:新建 SourceCode/Code2026/cad_mcp_aec,仍是普通 DLL(由 frame 运行时
LoadLibrary),但链接 CoreCommand_BAL_REMOTE(内含静态库 CoreLibrary),
因此可以 new / 修改这五类实体。cmake 照 PipelineCmd 的写法,并在顶层
cmakelists.txt 里挂在 cad_mcp_frame 同一处(C++11+ 才编,R180/R190 跳过)。

12 个工具(每个实体一个 create + 一个 update,外加 list/get):
  list_aec_entities / get_aec_entity
  create_aec_pipe   / update_aec_pipe
  create_aec_wall   / update_aec_wall
  create_aec_slab   / update_aec_slab
  create_aec_column / update_aec_column
  create_aec_beam   / update_aec_beam

单位约定:坐标与建筑尺寸用图纸单位;管道的 dn_mm / od_mm /
wall_thickness_mm 用毫米,内部按 AS_MUNIT 转米(与 BZGDDlg、AecPipeLineTool 一致)。
update_* 只改传入字段;管道只给一个端点时另一端沿用实体现有值。

实现上撞到三个坑,都已修:
1. MCP 工具在主线程消息里跑、不在命令上下文,写库必须先
   acDocManager->lockDocument(),否则 appendAcDbEntity 返回
   eLockViolation(0x140)。用 DocLock RAII 包起来。
2. 只读实体上取专业数据必须 GetSpecialtyData(AcDb::kForRead):
   该函数缺省参数是 kForWrite,第一次实现没注意,直接把 acad 崩了
   (%TEMP%\acminidump.dmp)。
3. SetSection() 会清掉 PIPE_THICK 等派生字段("只改 DN"会把壁厚清成 0);
   AecDbXSlab::SetRegionSlab(AcGePoint2dArray&) 内部连线不补闭合点,
   传点串必须自己首尾闭合。两处都按实测结果处理了。

验证方式:
- R230 Debug、Z2026 Debug 两个 preset 编译通过并自动部署
  (cad_mcp_aec.dll,12 个工厂函数 + g_mcpPluginApiVersion 都在导出表里)。
- AutoCAD 2020 实测(临时把工具合进部署目录的 cad-mcp-frame.json,测完还原):
  tools/list 返回 30 个工具;pcf_1.dwg 上 list 出 20 根管道;
  五种实体各建一次并用 get 读回参数一致;管道改 DN+单端点后壁厚保留;
  板改厚/标高生效;类型不匹配与非法 handle 返回 -32004 / -32002。
  详见 cad-mcp-aec.md 第 5 节的用例表。

已知限制:只支持这五类实体;新建实体要等一次 REGEN 才有 bbox;
工具没有事务包装;运行期工具描述需要把 cad-mcp-aec.json 的 tools 追加到
部署目录的 cad-mcp-frame.json(插件侧的 private_description 已内置同样的文案)。
2026-10-29 21:52:05 +08:00

284 lines
6.8 KiB
C++

#include "StdAfx.h"
#include "KTCad.h"
#include "aec_mcp_common.h"
#include "mcp_plugin_api.h"
namespace aec_mcp
{
// ===========================================================================
// 结果封装
// ===========================================================================
nlohmann::json okJson(const nlohmann::json& body)
{
// 统一信封:{"ok":true, ...业务字段}
nlohmann::json o = nlohmann::json::object();
o["ok"] = true;
if (body.is_object())
{
for (nlohmann::json::const_iterator it = body.begin(); it != body.end(); ++it)
o[it.key()] = it.value();
}
else if (!body.is_null())
{
o["data"] = body;
}
std::string s = o.dump();
return mcp_Tool_Utility::make_text_result(mcp_Tool_Utility::utf8ToWString(s));
}
nlohmann::json errJson(int code, const std::wstring& msg)
{
return mcp_Tool_Utility::make_error(code, msg);
}
// ===========================================================================
// 参数读取
// ===========================================================================
bool hasKey(const nlohmann::json& a, const char* key)
{
return a.is_object() && a.contains(key) && !a[key].is_null();
}
double numOr(const nlohmann::json& a, const char* key, double defVal)
{
if (!hasKey(a, key)) return defVal;
const nlohmann::json& v = a[key];
if (v.is_number()) return v.get<double>();
if (v.is_boolean()) return v.get<bool>() ? 1.0 : 0.0;
if (v.is_string())
{
try { return std::stod(v.get<std::string>()); }
catch (...) { return defVal; }
}
return defVal;
}
int intOr(const nlohmann::json& a, const char* key, int defVal)
{
return (int)numOr(a, key, (double)defVal);
}
std::wstring wstrOr(const nlohmann::json& a, const char* key, const wchar_t* defVal)
{
if (!hasKey(a, key) || !a[key].is_string()) return std::wstring(defVal ? defVal : L"");
return mcp_Tool_Utility::utf8ToWString(a[key].get<std::string>());
}
bool pointOf(const nlohmann::json& v, AcGePoint3d& pt)
{
if (v.is_object())
{
if (!v.contains("x") || !v.contains("y")) return false;
pt.x = v.value("x", 0.0);
pt.y = v.value("y", 0.0);
pt.z = v.value("z", 0.0);
return true;
}
if (v.is_array() && v.size() >= 2)
{
pt.x = v[0].get<double>();
pt.y = v[1].get<double>();
pt.z = (v.size() >= 3) ? v[2].get<double>() : 0.0;
return true;
}
return false;
}
bool readPoint(const nlohmann::json& a, const char* key, AcGePoint3d& pt)
{
if (!hasKey(a, key)) return false;
return pointOf(a[key], pt);
}
bool readPoint2dArray(const nlohmann::json& a, const char* key, AcGePoint2dArray& pts)
{
if (!hasKey(a, key) || !a[key].is_array()) return false;
const nlohmann::json& arr = a[key];
for (size_t i = 0; i < arr.size(); ++i)
{
const nlohmann::json& e = arr[i];
if (e.is_object() && e.contains("x") && e.contains("y"))
pts.append(AcGePoint2d(e.value("x", 0.0), e.value("y", 0.0)));
else if (e.is_array() && e.size() >= 2)
pts.append(AcGePoint2d(e[0].get<double>(), e[1].get<double>()));
else
return false;
}
return !pts.isEmpty();
}
// ===========================================================================
// 图纸 / 对象
// ===========================================================================
AcDbDatabase* workingDb()
{
return acdbHostApplicationServices()->workingDatabase();
}
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);
}
AcDbEntity* openByHandle(const std::string& handleStr, AcDb::OpenMode mode, AcDbObjectId* pIdOut)
{
if (handleStr.empty()) return NULL;
AcDbDatabase* pDb = workingDb();
if (pDb == NULL) return NULL;
std::wstring ws = utf8ToWstr(handleStr);
AcDbHandle h(ws.c_str());
AcDbObjectId id;
if (pDb->getAcDbObjectId(id, false, h) != Acad::eOk || id.isNull())
return NULL;
AcDbEntity* pEnt = NULL;
if (acdbOpenObject(pEnt, id, mode) != Acad::eOk)
return NULL;
if (pIdOut != NULL)
*pIdOut = id;
return pEnt;
}
std::string handleOf(AcDbObject* pObj)
{
if (pObj == NULL) return std::string();
AcDbHandle h;
pObj->getAcDbHandle(h);
ACHAR szBuf[64] = { 0 };
h.getIntoAsciiBuffer(szBuf, 64);
return acharToUtf8(szBuf);
}
Acad::ErrorStatus addToSpace(AcDbEntity* pEnt, AcDbObjectId* pIdOut, const std::wstring& space)
{
AcDbDatabase* pDb = workingDb();
if (pDb == NULL || pEnt == NULL)
return Acad::eInvalidInput;
AcDbBlockTable* pBT = NULL;
if (pDb->getBlockTable(pBT, AcDb::kForRead) != Acad::eOk || pBT == NULL)
return Acad::eInvalidInput;
AcDbObjectId idSpace;
Acad::ErrorStatus es = Acad::eKeyNotFound;
if (space.empty() || space == L"Model" || space == L"model")
{
es = pBT->getAt(ACDB_MODEL_SPACE, idSpace);
}
else
{
es = pBT->getAt(space.c_str(), idSpace);
}
pBT->close();
if (es != Acad::eOk)
return es;
AcDbBlockTableRecord* pRec = NULL;
es = acdbOpenObject(pRec, idSpace, AcDb::kForWrite);
if (es != Acad::eOk || pRec == NULL)
return es;
AcDbObjectId idNew;
es = pRec->appendAcDbEntity(idNew, pEnt);
pRec->close();
if (es != Acad::eOk)
return es;
if (pIdOut != NULL)
*pIdOut = idNew;
// appendAcDbEntity 不接管关闭;调用方仍然负责 close
return Acad::eOk;
}
AcDbObjectId layerIdByName(const std::wstring& name)
{
AcDbObjectId id;
if (name.empty()) return id;
AcDbDatabase* pDb = workingDb();
if (pDb == NULL) return id;
AcDbLayerTable* pTable = NULL;
if (pDb->getLayerTable(pTable, AcDb::kForRead) != Acad::eOk || pTable == NULL)
return id;
pTable->getAt(name.c_str(), id);
pTable->close();
return id;
}
// ===========================================================================
// 其它
// ===========================================================================
std::wstring utf8ToWstr(const std::string& s)
{
return mcp_Tool_Utility::utf8ToWString(s);
}
std::string acharToUtf8(const ACHAR* s)
{
if (s == NULL) return std::string();
#ifdef _UNICODE
return mcp_Tool_Utility::WStringToUtf8(std::wstring(s));
#else
return std::string(s);
#endif
}
std::string classNameOf(AcDbObject* pObj)
{
if (pObj == NULL || pObj->isA() == NULL) return std::string();
std::string n = acharToUtf8(pObj->isA()->name());
if (n.rfind("AecDb", 0) == 0) n = n.substr(5);
return n;
}
bool entityBBox(AcDbEntity* pEnt, AcGePoint3d& ptMin, AcGePoint3d& ptMax)
{
if (pEnt == NULL) return false;
try
{
AcDbExtents ext;
if (pEnt->getGeomExtents(ext) != Acad::eOk) return false;
ptMin = ext.minPoint();
ptMax = ext.maxPoint();
return true;
}
catch (...)
{
return false;
}
}
void touchEntity(AcDbEntity* pEnt)
{
if (pEnt == NULL) return;
// 几何改了要让显示缓存失效(AecDb* 的 2D/3D 缓存由 assertWriteEnabled 清),
// 再通知 CAD 重画这一段
pEnt->recordGraphicsModified(Adesk::kTrue);
}
} // namespace aec_mcp