现象: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 已内置同样的文案)。
284 lines
6.8 KiB
C++
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
|