docs: 增加 cad_mcp_aec 的说明与实测结果 #113
No files matched your search
@@ -0,0 +1 @@
|
||||
#include "StdAfx.h"
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#define WINVER 0x0600
|
||||
|
||||
#pragma warning(disable: 4786)
|
||||
|
||||
//- 'DEBUG workaround' below prevents the MFC or ATL #include-s from pulling
|
||||
//- in "Afx.h" that would force the debug CRT through #pragma-s.
|
||||
#if defined(_DEBUG) && !defined(AC_FULL_DEBUG)
|
||||
#define _DEBUG_WAS_DEFINED
|
||||
#undef _DEBUG
|
||||
#pragma message ("Compiling MFC / STL / ATL header files in release mode.")
|
||||
#endif
|
||||
|
||||
#pragma pack (push, 8)
|
||||
#pragma warning(disable: 4786 4996)
|
||||
|
||||
#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
|
||||
|
||||
#include <afxwin.h> // MFC core and standard components
|
||||
#include <afxext.h> // MFC extensions
|
||||
|
||||
#ifndef _AFX_NO_OLE_SUPPORT
|
||||
#include <afxole.h> // MFC OLE classes
|
||||
#include <afxodlgs.h> // MFC OLE dialog classes
|
||||
#include <afxdisp.h> // MFC Automation classes
|
||||
#endif
|
||||
|
||||
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
|
||||
#ifndef _AFX_NO_AFXCMN_SUPPORT
|
||||
#include <afxcmn.h> // MFC support for Windows Common Controls
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _DEBUG_WAS_DEFINED
|
||||
#define _DEBUG
|
||||
#undef _DEBUG_WAS_DEFINED
|
||||
#endif
|
||||
@@ -0,0 +1,283 @@
|
||||
#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
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// aec_mcp_common.h
|
||||
// cad_mcp_aec 插件的公共辅助层
|
||||
//
|
||||
// 与 cad_mcp_plugins 的 dwg_review_common 定位相同,但面向"编辑自定义实体":
|
||||
// 它要能 new / 改仓库的 AecDb* 实体,所以链接 CoreCommand.arx。
|
||||
//
|
||||
// 约定(写进工具描述里,供 LLM 读):
|
||||
// * 所有**坐标**与**建筑尺寸**(墙厚/墙高/板厚/柱高/梁宽…)用**图纸单位**;
|
||||
// * 管道/圆管的**公称直径、外径、壁厚**用 **mm**,内部按 AS_MUNIT 转成米,
|
||||
// 与仓库里所有管道对话框一致(BZGDDlg / AecPipeLineTool)。
|
||||
// ============================================================================
|
||||
|
||||
#include "KTCad.h"
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace aec_mcp
|
||||
{
|
||||
|
||||
// 错误码区间(与 dwg_review 保持一致的风格)
|
||||
enum ErrCode
|
||||
{
|
||||
kErrBadParam = -32001, // 参数非法
|
||||
kErrNotFound = -32002, // 实体/图层未找到
|
||||
kErrNoDocument = -32003, // 没有活动图纸
|
||||
kErrWrongType = -32004, // handle 指向的类型不对(比如传了柱子却调 update_pipe)
|
||||
kErrInternal = -32603 // 内部错误
|
||||
};
|
||||
|
||||
// ---------- 结果封装 ----------
|
||||
// okJson:把业务 json 塞进 MCP 的 text content(LLM 直接读到结构化结果)
|
||||
nlohmann::json okJson(const nlohmann::json& body);
|
||||
nlohmann::json errJson(int code, const std::wstring& msg);
|
||||
|
||||
// ---------- 参数读取 ----------
|
||||
bool hasKey(const nlohmann::json& a, const char* key);
|
||||
double numOr(const nlohmann::json& a, const char* key, double defVal);
|
||||
int intOr(const nlohmann::json& a, const char* key, int defVal);
|
||||
std::wstring wstrOr(const nlohmann::json& a, const char* key, const wchar_t* defVal);
|
||||
|
||||
// 点:接受 {"x":..,"y":..,"z":..} 或 [x, y, z] 两种写法;z 缺省 0
|
||||
bool pointOf(const nlohmann::json& v, AcGePoint3d& pt);
|
||||
bool readPoint(const nlohmann::json& a, const char* key, AcGePoint3d& pt);
|
||||
|
||||
// 点串:[[x,y],...] 或 [{"x":..,"y":..},...]
|
||||
bool readPoint2dArray(const nlohmann::json& a, const char* key, AcGePoint2dArray& pts);
|
||||
|
||||
// ---------- 图纸 / 对象 ----------
|
||||
AcDbDatabase* workingDb();
|
||||
|
||||
// 写数据库必须先拿文档锁:MCP 工具是在主线程的消息里跑的,不在命令上下文中,
|
||||
// 不锁会拿到 Acad::eLockViolation(0x140)。
|
||||
// 构造即加锁,析构解锁;嵌套调用安全(lockDocument 可重复加锁)。
|
||||
class DocLock
|
||||
{
|
||||
public:
|
||||
DocLock();
|
||||
~DocLock();
|
||||
bool isLocked() const { return m_bLocked; }
|
||||
|
||||
private:
|
||||
DocLock(const DocLock&);
|
||||
DocLock& operator=(const DocLock&);
|
||||
|
||||
bool m_bLocked;
|
||||
AcApDocument* m_pDoc;
|
||||
};
|
||||
|
||||
// 按 handle 打开实体(调用方负责 close)。失败返回 NULL。
|
||||
AcDbEntity* openByHandle(const std::string& handleStr, AcDb::OpenMode mode, AcDbObjectId* pIdOut = nullptr);
|
||||
|
||||
// 实体句柄字符串(大写十六进制),失败返回空串
|
||||
std::string handleOf(AcDbObject* pObj);
|
||||
|
||||
// 加入当前空间(默认模型空间),成功后 id 通过 pIdOut 返回
|
||||
Acad::ErrorStatus addToSpace(AcDbEntity* pEnt, AcDbObjectId* pIdOut, const std::wstring& space = L"");
|
||||
|
||||
// 图层名 -> ObjectId,找不到返回 kNull(不新建图层,避免污染图纸)
|
||||
AcDbObjectId layerIdByName(const std::wstring& name);
|
||||
|
||||
// ---------- 其它 ----------
|
||||
std::string acharToUtf8(const ACHAR* s);
|
||||
std::wstring utf8ToWstr(const std::string& s);
|
||||
|
||||
// 实体类名(不含 AecDb 前缀),如 "EwPipe"
|
||||
std::string classNameOf(AcDbObject* pObj);
|
||||
|
||||
// 包围盒(失败返回 false)
|
||||
bool entityBBox(AcDbEntity* pEnt, AcGePoint3d& ptMin, AcGePoint3d& ptMax);
|
||||
|
||||
// 修改后让显示/3D 缓存跟着更新
|
||||
void touchEntity(AcDbEntity* pEnt);
|
||||
|
||||
} // namespace aec_mcp
|
||||
@@ -0,0 +1,922 @@
|
||||
// ============================================================================
|
||||
// aec_mcp_entity_tools.cpp
|
||||
// cad_mcp_aec 的"建/改"工具:管道、墙、板、柱、梁
|
||||
//
|
||||
// 参数约定:
|
||||
// * 坐标与建筑尺寸(墙厚/墙高/板厚/柱高/梁宽…)用**图纸单位**;
|
||||
// * 管道的公称直径 / 外径 / 壁厚用 **mm**,内部按 AS_MUNIT 转米
|
||||
// (与 BZGDDlg、AecPipeLineTool 的既有写法一致)。
|
||||
//
|
||||
// 属性 key 用的是 EarThwormDef.h 里的 PIPE_DN / PIPE_OD / PIPE_THICK 等
|
||||
// (AecDbCurveBase.h 里包含的就是它;注意 inc/AecWPipe.h 里有同名但值不同的宏,
|
||||
// 本文件不要包含 AecWPipe.h,否则写进去的 key 会变)。
|
||||
// ============================================================================
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include "KTCad.h"
|
||||
#include "aec_mcp_common.h"
|
||||
#include "mcp_plugin_api.h"
|
||||
|
||||
#include "AecDbEwPipe.h"
|
||||
#include "AecDbWall.h"
|
||||
#include "AecDbSlab.h"
|
||||
#include "AecDbColumn.h"
|
||||
#include "AecDbBeam.h"
|
||||
#include "AecDbObjXDataMap.h"
|
||||
#include "KTSetting.h"
|
||||
|
||||
using namespace aec_mcp;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 参数应用(建/改共用)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// 管道:至少要有起终点与公称直径。其余参数缺省时保留原值(改的时候)。
|
||||
bool applyPipe(AecDbEwPipe* p, const nlohmann::json& a, bool bCreate, std::wstring& err)
|
||||
{
|
||||
AcGePoint3d ptS, ptE;
|
||||
bool bHasS = readPoint(a, "start", ptS);
|
||||
bool bHasE = readPoint(a, "end", ptE);
|
||||
|
||||
if (bCreate && (!bHasS || !bHasE))
|
||||
{
|
||||
err = L"create_aec_pipe 需要 start / end({\"x\",\"y\",\"z\"} 或 [x,y,z])";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 改的时候只给一个端点:另一个沿用实体现有端点,别把管拉回原点
|
||||
if (!bCreate && (bHasS != bHasE))
|
||||
{
|
||||
const AcDbCurve* pCrv = AcDbCurve::cast(p);
|
||||
AcGePoint3d ptCurS, ptCurE;
|
||||
if (pCrv != NULL && pCrv->getStartPoint(ptCurS) == Acad::eOk
|
||||
&& pCrv->getEndPoint(ptCurE) == Acad::eOk)
|
||||
{
|
||||
if (!bHasS) { ptS = ptCurS; bHasS = true; }
|
||||
if (!bHasE) { ptE = ptCurE; bHasE = true; }
|
||||
}
|
||||
}
|
||||
|
||||
if (bHasS && bHasE)
|
||||
{
|
||||
if (ptS.distanceTo(ptE) < 1e-9)
|
||||
{
|
||||
err = L"start 与 end 重合,管道长度为 0";
|
||||
return false;
|
||||
}
|
||||
p->SetBaseCurve(ptS, ptE);
|
||||
}
|
||||
|
||||
AecDbObjXDataMap* pX = p->GetSpecialtyData();
|
||||
if (pX == NULL)
|
||||
{
|
||||
err = L"取专业数据失败(GetSpecialtyData 返回空)";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 现有的截面参数(内部存的是米),下面按"传了就用新的、没传就保持"合并后统一重写:
|
||||
// SetSection() 会把 PIPE_THICK/PIPE_OD 之类的派生字段清掉,所以必须先取再补。
|
||||
double dDnOld = 0.0, dOdOld = 0.0, dThOld = 0.0;
|
||||
pX->GetAt(PIPE_DN, dDnOld);
|
||||
pX->GetAt(PIPE_OD, dOdOld);
|
||||
pX->GetAt(PIPE_THICK, dThOld);
|
||||
|
||||
const double dDn = hasKey(a, "dn_mm") ? numOr(a, "dn_mm", 0.0) : dDnOld * 1000.0;
|
||||
const double dOd = hasKey(a, "od_mm") ? numOr(a, "od_mm", 0.0) : dOdOld * 1000.0;
|
||||
const double dTh = hasKey(a, "wall_thickness_mm") ? numOr(a, "wall_thickness_mm", 0.0) : dThOld * 1000.0;
|
||||
|
||||
if (bCreate || hasKey(a, "dn_mm"))
|
||||
{
|
||||
if (dDn <= 0.0)
|
||||
{
|
||||
err = L"create_aec_pipe 需要 dn_mm(公称直径,mm)";
|
||||
return false;
|
||||
}
|
||||
// 与 BZGDDlg 一致:截面圆用 DN(米制)
|
||||
p->SetSection(AS_MUNIT(dDn), 0.0);
|
||||
}
|
||||
|
||||
pX->SetAt(KEY_2DSTYLE, 1);
|
||||
|
||||
// 统一重写三项:SetSection 之后无论传没传都要落到专业数据里
|
||||
if (dDn > 0.0) pX->SetAt(PIPE_DN, AS_MUNIT(dDn));
|
||||
if (dOd > 0.0) pX->SetAt(PIPE_OD, AS_MUNIT(dOd));
|
||||
pX->SetAt(PIPE_THICK, AS_MUNIT(dTh));
|
||||
if (hasKey(a, "system"))
|
||||
pX->SetAt(PIPE_SYS, wstrOr(a, "system", L"").c_str());
|
||||
if (hasKey(a, "material"))
|
||||
pX->SetAt(PIPE_MATERIAL, wstrOr(a, "material", L"").c_str());
|
||||
if (hasKey(a, "color_index"))
|
||||
pX->SetAt(_T("COLOR"), intOr(a, "color_index", 0));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 墙:起点-终点 + 厚/高/底标高(+ 对齐、材质)
|
||||
bool applyWall(AecDbXWall* p, const nlohmann::json& a, bool bCreate, std::wstring& err)
|
||||
{
|
||||
AcGePoint3d ptS, ptE;
|
||||
const bool bHasS = readPoint(a, "start", ptS);
|
||||
const bool bHasE = readPoint(a, "end", ptE);
|
||||
|
||||
if (bCreate && (!bHasS || !bHasE))
|
||||
{
|
||||
err = L"create_aec_wall 需要 start / end";
|
||||
return false;
|
||||
}
|
||||
if (bHasS && bHasE)
|
||||
{
|
||||
if (ptS.distanceTo(ptE) < 1e-9)
|
||||
{
|
||||
err = L"start 与 end 重合,墙长为 0";
|
||||
return false;
|
||||
}
|
||||
p->SetBaseCurve(ptS, ptE);
|
||||
}
|
||||
|
||||
WallData data;
|
||||
::memset(&data, 0, sizeof(WallData));
|
||||
p->GetWallData(data);
|
||||
|
||||
const double dThick = numOr(a, "thickness", bCreate ? 0.0 : data.dThick);
|
||||
const double dH = numOr(a, "height", bCreate ? 0.0 : data.dH);
|
||||
const double dElev = numOr(a, "bottom_elevation", bCreate ? 0.0 : data.dBomEl);
|
||||
|
||||
if (dThick <= 0.0 || dH <= 0.0)
|
||||
{
|
||||
err = L"墙需要 thickness(厚)与 height(高),且都 > 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
EAlign eAlign = data.kAlign;
|
||||
if (hasKey(a, "align"))
|
||||
{
|
||||
const std::wstring s = wstrOr(a, "align", L"center");
|
||||
if (s == L"left") eAlign = kLeft;
|
||||
else if (s == L"right") eAlign = kRight;
|
||||
else eAlign = kMid;
|
||||
}
|
||||
|
||||
p->SetWallData(dThick, dH, dElev, eAlign);
|
||||
|
||||
if (hasKey(a, "material"))
|
||||
p->GetSpecialtyData()->SetAt(KEY_MATERIAL, wstrOr(a, "material", L"").c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 板:闭合点串 + 板厚 + 板底标高
|
||||
bool applySlab(AecDbXSlab* p, const nlohmann::json& a, bool bCreate, std::wstring& err)
|
||||
{
|
||||
AcGePoint2dArray arPts;
|
||||
const bool bHasPts = readPoint2dArray(a, "points", arPts);
|
||||
|
||||
if (bCreate && !bHasPts)
|
||||
{
|
||||
err = L"create_aec_slab 需要 points:[[x,y],...](至少 3 个点,可不闭合)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bHasPts && arPts.length() < 3)
|
||||
{
|
||||
err = L"points 至少要有 3 个点";
|
||||
return false;
|
||||
}
|
||||
|
||||
// SetRegionSlab() 内部用 CreatePolygonRegionOnly() 连线,而后者只连
|
||||
// [0, n-2] 段、不自动闭合(闭合点只写进了 KEY_POINTS 字符串)。
|
||||
// 所以这里必须自己把首点补到末尾,否则 region 建不出来、直接返回 false。
|
||||
if (bHasPts && arPts.first().distanceTo(arPts.last()) > 1e-9)
|
||||
arPts.append(arPts.first());
|
||||
|
||||
const double dThick = numOr(a, "thickness", 0.0);
|
||||
const double dElev = numOr(a, "bottom_elevation", 0.0);
|
||||
if (bCreate && dThick <= 0.0)
|
||||
{
|
||||
err = L"create_aec_slab 需要 thickness(板厚)> 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bHasPts)
|
||||
{
|
||||
// 中心点缺省取各点平均
|
||||
AcGePoint3d ptCen(0.0, 0.0, 0.0);
|
||||
if (!readPoint(a, "center", ptCen))
|
||||
{
|
||||
for (int i = 0; i < arPts.length(); ++i)
|
||||
{
|
||||
ptCen.x += arPts.at(i).x;
|
||||
ptCen.y += arPts.at(i).y;
|
||||
}
|
||||
ptCen.x /= (double)arPts.length();
|
||||
ptCen.y /= (double)arPts.length();
|
||||
ptCen.z = 0.0;
|
||||
}
|
||||
|
||||
if (!p->SetRegionSlab(arPts, dThick, dElev, ptCen))
|
||||
{
|
||||
err = L"SetRegionSlab 失败(点串可能自交或退化)";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!bCreate)
|
||||
{
|
||||
// 只改板厚/标高:SetRegionSlab 的数组重载必须重新给轮廓,
|
||||
// 所以这里把存盘时记在 KEY_POINTS 里的轮廓串取出来,走字符串重载。
|
||||
const AecDbObjXDataMap* pX = p->GetSpecialtyData(AcDb::kForRead);
|
||||
CString sPts;
|
||||
AcGePoint3d ptOrg(0.0, 0.0, 0.0);
|
||||
if (pX != NULL)
|
||||
{
|
||||
pX->GetAt(KEY_POINTS, sPts);
|
||||
pX->GetAt(KEY_ORGPT, ptOrg);
|
||||
}
|
||||
if (sPts.IsEmpty())
|
||||
{
|
||||
err = L"这块板没有记录轮廓(KEY_POINTS 为空),无法在不给 points 的情况下改尺寸";
|
||||
return false;
|
||||
}
|
||||
|
||||
double dThickNow = dThick;
|
||||
p->GetAt(KEY_BOMTHICK, dThickNow);
|
||||
const double dThNew = hasKey(a, "thickness") ? dThick : (dThickNow > 0.0 ? dThickNow : dThick);
|
||||
const double dElevNew = hasKey(a, "bottom_elevation") ? dElev : ptOrg.z;
|
||||
|
||||
AcGePoint3d ptCen = ptOrg;
|
||||
readPoint(a, "center", ptCen);
|
||||
|
||||
if (!p->SetRegionSlab((LPCTSTR)sPts, dThNew, dElevNew, ptCen))
|
||||
{
|
||||
err = L"按已有轮廓重建板失败(轮廓串可能已损坏)";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasKey(a, "material"))
|
||||
p->GetSpecialtyData()->SetAt(KEY_MATERIAL, wstrOr(a, "material", L"").c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 柱:位置 + 高 + 截面(长 x 宽)+ 旋转
|
||||
bool applyColumn(AecDbColumn* p, const nlohmann::json& a, bool bCreate, std::wstring& err)
|
||||
{
|
||||
AcGePoint3d ptPos;
|
||||
const bool bHasPos = readPoint(a, "position", ptPos);
|
||||
|
||||
ColumnParam data;
|
||||
::memset(&data, 0, sizeof(ColumnParam));
|
||||
p->GetColumnData(data);
|
||||
|
||||
const bool bAny = bHasPos || hasKey(a, "height") || hasKey(a, "length")
|
||||
|| hasKey(a, "width") || hasKey(a, "rotation_deg");
|
||||
if (bCreate && !bHasPos)
|
||||
{
|
||||
err = L"create_aec_column 需要 position(柱底中心点)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bAny)
|
||||
return true; // 只改了材质之类的场景
|
||||
|
||||
const AcGePoint3d ptBase = bHasPos ? ptPos : p->GetPosition();
|
||||
const double dH = numOr(a, "height", bCreate ? 0.0 : p->GetHeight());
|
||||
const AcGePoint2d rc = p->GetRect();
|
||||
const double dL = numOr(a, "length", rc.x);
|
||||
const double dW = numOr(a, "width", rc.y);
|
||||
const double dRot = hasKey(a, "rotation_deg")
|
||||
? numOr(a, "rotation_deg", 0.0) * PI / 180.0
|
||||
: p->GetRotation();
|
||||
|
||||
if (dH <= 0.0 || dL <= 0.0 || dW <= 0.0)
|
||||
{
|
||||
err = L"柱需要 height / length / width,且都 > 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
p->SetColumnData(ptBase, dL, dW, dH, dRot);
|
||||
|
||||
if (hasKey(a, "material"))
|
||||
p->GetSpecialtyData()->SetAt(KEY_MATERIAL, wstrOr(a, "material", L"").c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 梁:起点-终点 + 截面宽 x 高
|
||||
bool applyBeam(AecDbBeam* p, const nlohmann::json& a, bool bCreate, std::wstring& err)
|
||||
{
|
||||
AcGePoint3d ptS, ptE;
|
||||
const bool bHasS = readPoint(a, "start", ptS);
|
||||
const bool bHasE = readPoint(a, "end", ptE);
|
||||
|
||||
if (bCreate && (!bHasS || !bHasE))
|
||||
{
|
||||
err = L"create_aec_beam 需要 start / end";
|
||||
return false;
|
||||
}
|
||||
if (bHasS && bHasE)
|
||||
{
|
||||
if (ptS.distanceTo(ptE) < 1e-9)
|
||||
{
|
||||
err = L"start 与 end 重合,梁长为 0";
|
||||
return false;
|
||||
}
|
||||
p->SetBaseCurve(ptS, ptE);
|
||||
}
|
||||
|
||||
ColumnParam data;
|
||||
::memset(&data, 0, sizeof(ColumnParam));
|
||||
p->GetBeamData(data);
|
||||
|
||||
// GetBeamData 把截面尺寸放在 m_dSizes[0](宽)/ m_dSizes[1](高)
|
||||
const double dW = numOr(a, "width", bCreate ? 0.0 : data.m_dSizes[0]);
|
||||
const double dH = numOr(a, "height", bCreate ? 0.0 : data.m_dSizes[1]);
|
||||
|
||||
if (dW <= 0.0 || dH <= 0.0)
|
||||
{
|
||||
err = L"梁需要 width(宽)与 height(高),且都 > 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
p->SetBeamData(dW, dH);
|
||||
|
||||
if (hasKey(a, "material"))
|
||||
p->GetSpecialtyData()->SetAt(KEY_MATERIAL, wstrOr(a, "material", L"").c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 共用收尾:设图层、加入空间、回包
|
||||
// ---------------------------------------------------------------------------
|
||||
nlohmann::json finishCreate(AcDbEntity* pEnt, const nlohmann::json& a, const char* pszType)
|
||||
{
|
||||
if (hasKey(a, "layer"))
|
||||
{
|
||||
const std::wstring sLayer = wstrOr(a, "layer", L"");
|
||||
if (!sLayer.empty())
|
||||
{
|
||||
AcDbObjectId idLay = layerIdByName(sLayer);
|
||||
if (!idLay.isNull())
|
||||
pEnt->setLayer(idLay);
|
||||
}
|
||||
}
|
||||
|
||||
AcDbObjectId idNew;
|
||||
Acad::ErrorStatus es = addToSpace(pEnt, &idNew);
|
||||
|
||||
// 句柄要**入图之后**才有值:append 之前 pEnt 还没 handle,取出来是 "0"
|
||||
const std::string sHandle = (es == Acad::eOk) ? handleOf(pEnt) : std::string();
|
||||
pEnt->close();
|
||||
|
||||
if (es != Acad::eOk)
|
||||
{
|
||||
delete pEnt;
|
||||
return errJson(kErrInternal, std::wstring(L"加入模型空间失败,err=") + std::to_wstring((int)es));
|
||||
}
|
||||
|
||||
nlohmann::json body;
|
||||
body["type"] = pszType;
|
||||
body["handle"] = sHandle;
|
||||
return okJson(body);
|
||||
}
|
||||
|
||||
// 按 handle 打开并确认类型,失败时直接给出可返回的错误
|
||||
template <typename T>
|
||||
T* openTyped(const nlohmann::json& a, const wchar_t* pszExpect, nlohmann::json& errOut)
|
||||
{
|
||||
if (!hasKey(a, "handle"))
|
||||
{
|
||||
errOut = errJson(kErrBadParam, L"缺少 handle(实体句柄,十六进制字符串)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const std::string sHandle = a["handle"].get<std::string>();
|
||||
AcDbEntity* pEnt = openByHandle(sHandle, AcDb::kForWrite);
|
||||
if (pEnt == NULL)
|
||||
{
|
||||
errOut = errJson(kErrNotFound, std::wstring(L"按 handle 打不开实体:") + utf8ToWstr(sHandle));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
T* pTyped = T::cast(pEnt);
|
||||
if (pTyped == NULL)
|
||||
{
|
||||
const std::string sCls = classNameOf(pEnt);
|
||||
pEnt->close();
|
||||
errOut = errJson(kErrWrongType,
|
||||
std::wstring(L"handle ") + utf8ToWstr(sHandle) + L" 的实体是 " +
|
||||
utf8ToWstr(sCls) + L",不是 " + pszExpect);
|
||||
return NULL;
|
||||
}
|
||||
return pTyped;
|
||||
}
|
||||
|
||||
nlohmann::json finishUpdate(AcDbObject* pObj, const std::string& sHandle, const char* pszType)
|
||||
{
|
||||
touchEntity(AcDbEntity::cast(pObj));
|
||||
const std::string sH = sHandle.empty() ? handleOf(pObj) : sHandle;
|
||||
pObj->close();
|
||||
|
||||
nlohmann::json body;
|
||||
body["type"] = pszType;
|
||||
body["handle"] = sH;
|
||||
body["updated"] = true;
|
||||
return okJson(body);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ===========================================================================
|
||||
// 管道
|
||||
// ===========================================================================
|
||||
class ToolCreatePipe : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "create_aec_pipe"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "在当前图纸新建一根 Envi3D 管道(AecDbEwPipe)。"
|
||||
"坐标用图纸单位,dn_mm/od_mm/wall_thickness_mm 用毫米。"
|
||||
"返回新实体的 handle,后续可用 update_aec_pipe 修改。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start": { "type": "object", "description": "起点 {\"x\",\"y\",\"z\"},图纸单位;也可用 [x,y,z]" },
|
||||
"end": { "type": "object", "description": "终点,格式同 start" },
|
||||
"dn_mm": { "type": "number", "description": "公称直径 DN,毫米" },
|
||||
"od_mm": { "type": "number", "description": "外径,毫米(缺省不改)" },
|
||||
"wall_thickness_mm": { "type": "number", "description": "壁厚,毫米(缺省不改)" },
|
||||
"system": { "type": "string", "description": "管道系统名,如 给水/排水" },
|
||||
"material": { "type": "string", "description": "材质名" },
|
||||
"color_index": { "type": "integer", "description": "颜色索引(AutoCAD 颜色号)" },
|
||||
"layer": { "type": "string", "description": "图层名,留空用当前图层" }
|
||||
},
|
||||
"required": [ "start", "end", "dn_mm" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
AecDbEwPipe* pPipe = new AecDbEwPipe();
|
||||
std::wstring err;
|
||||
if (!applyPipe(pPipe, args, true, err))
|
||||
{
|
||||
delete pPipe;
|
||||
return errJson(kErrBadParam, err);
|
||||
}
|
||||
return finishCreate(pPipe, args, "pipe");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolCreatePipe, CreateAecPipeTool)
|
||||
|
||||
class ToolUpdatePipe : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "update_aec_pipe"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "修改已存在的 Envi3D 管道(AecDbEwPipe)。只传要改的字段,未传的保持原值。"
|
||||
"改起终点会重算几何;改 dn_mm 会重建截面。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handle": { "type": "string", "description": "要修改的管道句柄(十六进制)" },
|
||||
"start": { "type": "object", "description": "新起点(可选)" },
|
||||
"end": { "type": "object", "description": "新终点(可选)" },
|
||||
"dn_mm": { "type": "number", "description": "新公称直径,毫米(可选)" },
|
||||
"od_mm": { "type": "number", "description": "新外径,毫米(可选)" },
|
||||
"wall_thickness_mm": { "type": "number", "description": "新壁厚,毫米(可选)" },
|
||||
"system": { "type": "string", "description": "管道系统名(可选)" },
|
||||
"material": { "type": "string", "description": "材质(可选)" },
|
||||
"color_index": { "type": "integer", "description": "颜色索引(可选)" }
|
||||
},
|
||||
"required": [ "handle" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
nlohmann::json err;
|
||||
AecDbEwPipe* pPipe = openTyped<AecDbEwPipe>(args, L"AecDbEwPipe", err);
|
||||
if (pPipe == NULL) return err;
|
||||
|
||||
std::wstring msg;
|
||||
if (!applyPipe(pPipe, args, false, msg))
|
||||
{
|
||||
pPipe->close();
|
||||
return errJson(kErrBadParam, msg);
|
||||
}
|
||||
return finishUpdate(pPipe, args["handle"].get<std::string>(), "pipe");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolUpdatePipe, UpdateAecPipeTool)
|
||||
|
||||
// ===========================================================================
|
||||
// 墙
|
||||
// ===========================================================================
|
||||
class ToolCreateWall : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "create_aec_wall"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "新建 Envi3D 墙体(AecDbXWall)。沿 start→end 建墙,尺寸用图纸单位,"
|
||||
"bottom_elevation 是墙底标高;align 决定墙厚在基线哪一侧。返回 handle。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start": { "type": "object", "description": "墙基线起点 {x,y,z}" },
|
||||
"end": { "type": "object", "description": "墙基线终点 {x,y,z}" },
|
||||
"thickness": { "type": "number", "description": "墙厚,图纸单位" },
|
||||
"height": { "type": "number", "description": "墙高,图纸单位" },
|
||||
"bottom_elevation": { "type": "number", "description": "墙底标高,图纸单位,缺省 0" },
|
||||
"align": { "type": "string", "enum": [ "left", "center", "right" ], "description": "对齐方式,缺省 center" },
|
||||
"material": { "type": "string", "description": "材质" },
|
||||
"layer": { "type": "string", "description": "图层名" }
|
||||
},
|
||||
"required": [ "start", "end", "thickness", "height" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
AecDbXWall* pWall = new AecDbXWall();
|
||||
std::wstring err;
|
||||
if (!applyWall(pWall, args, true, err))
|
||||
{
|
||||
delete pWall;
|
||||
return errJson(kErrBadParam, err);
|
||||
}
|
||||
return finishCreate(pWall, args, "wall");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolCreateWall, CreateAecWallTool)
|
||||
|
||||
class ToolUpdateWall : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "update_aec_wall"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "修改已存在的 Envi3D 墙体(AecDbXWall)。只传要改的字段。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handle": { "type": "string", "description": "墙体句柄" },
|
||||
"start": { "type": "object", "description": "新起点(可选)" },
|
||||
"end": { "type": "object", "description": "新终点(可选)" },
|
||||
"thickness": { "type": "number", "description": "新墙厚(可选)" },
|
||||
"height": { "type": "number", "description": "新墙高(可选)" },
|
||||
"bottom_elevation": { "type": "number", "description": "新墙底标高(可选)" },
|
||||
"align": { "type": "string", "enum": [ "left", "center", "right" ], "description": "对齐(可选)" },
|
||||
"material": { "type": "string", "description": "材质(可选)" }
|
||||
},
|
||||
"required": [ "handle" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
nlohmann::json err;
|
||||
AecDbXWall* pWall = openTyped<AecDbXWall>(args, L"AecDbXWall", err);
|
||||
if (pWall == NULL) return err;
|
||||
|
||||
std::wstring msg;
|
||||
if (!applyWall(pWall, args, false, msg))
|
||||
{
|
||||
pWall->close();
|
||||
return errJson(kErrBadParam, msg);
|
||||
}
|
||||
return finishUpdate(pWall, args["handle"].get<std::string>(), "wall");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolUpdateWall, UpdateAecWallTool)
|
||||
|
||||
// ===========================================================================
|
||||
// 板
|
||||
// ===========================================================================
|
||||
class ToolCreateSlab : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "create_aec_slab"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "新建 Envi3D 板/底板(AecDbXSlab)。points 是水平轮廓点串(可不闭合),"
|
||||
"thickness 是板厚,bottom_elevation 是板底标高,均用图纸单位。返回 handle。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"points": { "type": "array", "description": "轮廓点 [[x,y],...],至少 3 个",
|
||||
"items": { "type": "array", "items": { "type": "number" } } },
|
||||
"thickness": { "type": "number", "description": "板厚,图纸单位" },
|
||||
"bottom_elevation": { "type": "number", "description": "板底标高,缺省 0" },
|
||||
"center": { "type": "object", "description": "板位置参考点 {x,y,z},缺省取点串平均值" },
|
||||
"material": { "type": "string", "description": "材质" },
|
||||
"layer": { "type": "string", "description": "图层名" }
|
||||
},
|
||||
"required": [ "points", "thickness" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
AecDbXSlab* pSlab = new AecDbXSlab();
|
||||
std::wstring err;
|
||||
if (!applySlab(pSlab, args, true, err))
|
||||
{
|
||||
delete pSlab;
|
||||
return errJson(kErrBadParam, err);
|
||||
}
|
||||
return finishCreate(pSlab, args, "slab");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolCreateSlab, CreateAecSlabTool)
|
||||
|
||||
class ToolUpdateSlab : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "update_aec_slab"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "修改已存在的 Envi3D 板(AecDbXSlab)。传 points 会重做轮廓,"
|
||||
"否则只改板厚/标高/材质。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handle": { "type": "string", "description": "板句柄" },
|
||||
"points": { "type": "array", "description": "新轮廓点(可选)",
|
||||
"items": { "type": "array", "items": { "type": "number" } } },
|
||||
"thickness": { "type": "number", "description": "新板厚(可选)" },
|
||||
"bottom_elevation": { "type": "number", "description": "新板底标高(可选)" },
|
||||
"center": { "type": "object", "description": "参考点(可选)" },
|
||||
"material": { "type": "string", "description": "材质(可选)" }
|
||||
},
|
||||
"required": [ "handle" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
nlohmann::json err;
|
||||
AecDbXSlab* pSlab = openTyped<AecDbXSlab>(args, L"AecDbXSlab", err);
|
||||
if (pSlab == NULL) return err;
|
||||
|
||||
std::wstring msg;
|
||||
if (!applySlab(pSlab, args, false, msg))
|
||||
{
|
||||
pSlab->close();
|
||||
return errJson(kErrBadParam, msg);
|
||||
}
|
||||
return finishUpdate(pSlab, args["handle"].get<std::string>(), "slab");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolUpdateSlab, UpdateAecSlabTool)
|
||||
|
||||
// ===========================================================================
|
||||
// 柱
|
||||
// ===========================================================================
|
||||
class ToolCreateColumn : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "create_aec_column"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "新建矩形柱(AecDbColumn)。position 是柱底中心点,height 是柱高,"
|
||||
"length/width 是截面两个方向尺寸,rotation_deg 是绕 Z 轴转角(度)。尺寸用图纸单位。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"position": { "type": "object", "description": "柱底中心点 {x,y,z}" },
|
||||
"height": { "type": "number", "description": "柱高,图纸单位" },
|
||||
"length": { "type": "number", "description": "截面长(X 向),图纸单位" },
|
||||
"width": { "type": "number", "description": "截面宽(Y 向),图纸单位" },
|
||||
"rotation_deg": { "type": "number", "description": "绕 Z 轴旋转角度(度),缺省 0" },
|
||||
"material": { "type": "string", "description": "材质" },
|
||||
"layer": { "type": "string", "description": "图层名" }
|
||||
},
|
||||
"required": [ "position", "height", "length", "width" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
AecDbColumn* pCol = new AecDbColumn();
|
||||
std::wstring err;
|
||||
if (!applyColumn(pCol, args, true, err))
|
||||
{
|
||||
delete pCol;
|
||||
return errJson(kErrBadParam, err);
|
||||
}
|
||||
return finishCreate(pCol, args, "column");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolCreateColumn, CreateAecColumnTool)
|
||||
|
||||
class ToolUpdateColumn : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "update_aec_column"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "修改已存在的矩形柱(AecDbColumn)。只传要改的字段。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handle": { "type": "string", "description": "柱句柄" },
|
||||
"position": { "type": "object", "description": "新柱底中心点(可选)" },
|
||||
"height": { "type": "number", "description": "新柱高(可选)" },
|
||||
"length": { "type": "number", "description": "新截面长(可选)" },
|
||||
"width": { "type": "number", "description": "新截面宽(可选)" },
|
||||
"rotation_deg": { "type": "number", "description": "新旋转角度(可选)" },
|
||||
"material": { "type": "string", "description": "材质(可选)" }
|
||||
},
|
||||
"required": [ "handle" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
nlohmann::json err;
|
||||
AecDbColumn* pCol = openTyped<AecDbColumn>(args, L"AecDbColumn", err);
|
||||
if (pCol == NULL) return err;
|
||||
|
||||
std::wstring msg;
|
||||
if (!applyColumn(pCol, args, false, msg))
|
||||
{
|
||||
pCol->close();
|
||||
return errJson(kErrBadParam, msg);
|
||||
}
|
||||
return finishUpdate(pCol, args["handle"].get<std::string>(), "column");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolUpdateColumn, UpdateAecColumnTool)
|
||||
|
||||
// ===========================================================================
|
||||
// 梁
|
||||
// ===========================================================================
|
||||
class ToolCreateBeam : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "create_aec_beam"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "新建梁(AecDbBeam)。沿 start→end 建梁,width/height 是截面尺寸,用图纸单位。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start": { "type": "object", "description": "梁起点 {x,y,z}" },
|
||||
"end": { "type": "object", "description": "梁终点 {x,y,z}" },
|
||||
"width": { "type": "number", "description": "梁宽,图纸单位" },
|
||||
"height": { "type": "number", "description": "梁高,图纸单位" },
|
||||
"material": { "type": "string", "description": "材质" },
|
||||
"layer": { "type": "string", "description": "图层名" }
|
||||
},
|
||||
"required": [ "start", "end", "width", "height" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
AecDbBeam* pBeam = new AecDbBeam();
|
||||
std::wstring err;
|
||||
if (!applyBeam(pBeam, args, true, err))
|
||||
{
|
||||
delete pBeam;
|
||||
return errJson(kErrBadParam, err);
|
||||
}
|
||||
return finishCreate(pBeam, args, "beam");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolCreateBeam, CreateAecBeamTool)
|
||||
|
||||
class ToolUpdateBeam : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "update_aec_beam"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "修改已存在的梁(AecDbBeam)。只传要改的字段。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handle": { "type": "string", "description": "梁句柄" },
|
||||
"start": { "type": "object", "description": "新起点(可选)" },
|
||||
"end": { "type": "object", "description": "新终点(可选)" },
|
||||
"width": { "type": "number", "description": "新梁宽(可选)" },
|
||||
"height": { "type": "number", "description": "新梁高(可选)" },
|
||||
"material": { "type": "string", "description": "材质(可选)" }
|
||||
},
|
||||
"required": [ "handle" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
DocLock lock;
|
||||
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
|
||||
|
||||
nlohmann::json err;
|
||||
AecDbBeam* pBeam = openTyped<AecDbBeam>(args, L"AecDbBeam", err);
|
||||
if (pBeam == NULL) return err;
|
||||
|
||||
std::wstring msg;
|
||||
if (!applyBeam(pBeam, args, false, msg))
|
||||
{
|
||||
pBeam->close();
|
||||
return errJson(kErrBadParam, msg);
|
||||
}
|
||||
return finishUpdate(pBeam, args["handle"].get<std::string>(), "beam");
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolUpdateBeam, UpdateAecBeamTool)
|
||||
@@ -0,0 +1,335 @@
|
||||
// ============================================================================
|
||||
// aec_mcp_query.cpp
|
||||
// cad_mcp_aec 的"读"工具
|
||||
// list_aec_entities : 按类型列出本插件支持的 Envi3D 实体(handle/layer/bbox/关键参数)
|
||||
// get_aec_entity : 按 handle 取单个实体的详细参数
|
||||
//
|
||||
// 返回统一分页信封:{ total, returned, offset, items:[...] },实体 ID 用 handle。
|
||||
// ============================================================================
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include "KTCad.h"
|
||||
#include "aec_mcp_common.h"
|
||||
#include "mcp_plugin_api.h"
|
||||
|
||||
#include "AecDbEwPipe.h"
|
||||
#include "AecDbWall.h"
|
||||
#include "AecDbSlab.h"
|
||||
#include "AecDbColumn.h"
|
||||
#include "AecDbBeam.h"
|
||||
#include "AecDbObjXDataMap.h"
|
||||
|
||||
using namespace aec_mcp;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const char* kTypePipe = "pipe";
|
||||
const char* kTypeWall = "wall";
|
||||
const char* kTypeSlab = "slab";
|
||||
const char* kTypeColumn = "column";
|
||||
const char* kTypeBeam = "beam";
|
||||
|
||||
// 实体 -> 五个受支持类型之一;不是这几类返回空串
|
||||
std::string supportedTypeOf(AcDbEntity* pEnt)
|
||||
{
|
||||
if (pEnt == NULL) return std::string();
|
||||
|
||||
// 判定顺序按继承深度:梁/墙/板都派生自 AecDbEwPipeBase,必须先判具体类
|
||||
if (AecDbEwPipe::cast(pEnt)) return kTypePipe;
|
||||
if (AecDbXWall::cast(pEnt)) return kTypeWall;
|
||||
if (AecDbXSlab::cast(pEnt)) return kTypeSlab;
|
||||
if (AecDbColumn::cast(pEnt)) return kTypeColumn;
|
||||
if (AecDbBeam::cast(pEnt)) return kTypeBeam;
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool typeWanted(const std::string& sType, const std::string& sFilter)
|
||||
{
|
||||
if (sFilter.empty() || sFilter == "all") return true;
|
||||
return sType == sFilter;
|
||||
}
|
||||
|
||||
nlohmann::json pointJson(const AcGePoint3d& pt)
|
||||
{
|
||||
nlohmann::json o;
|
||||
o["x"] = pt.x;
|
||||
o["y"] = pt.y;
|
||||
o["z"] = pt.z;
|
||||
return o;
|
||||
}
|
||||
|
||||
// 取起终点(这些实体都有 base curve)
|
||||
bool baseCurveEnds(AcDbEntity* pEnt, AcGePoint3d& ptS, AcGePoint3d& ptE)
|
||||
{
|
||||
AcDbCurve* pCrv = AcDbCurve::cast(pEnt);
|
||||
if (pCrv == NULL) return false;
|
||||
return pCrv->getStartPoint(ptS) == Acad::eOk && pCrv->getEndPoint(ptE) == Acad::eOk;
|
||||
}
|
||||
|
||||
// 各类型的关键参数(读取时按 kForRead 打开的专业数据)
|
||||
nlohmann::json paramsOf(AcDbEntity* pEnt, const std::string& sType)
|
||||
{
|
||||
nlohmann::json p = nlohmann::json::object();
|
||||
|
||||
if (sType == kTypePipe)
|
||||
{
|
||||
AecDbEwPipe* pPipe = AecDbEwPipe::cast(pEnt);
|
||||
if (pPipe == NULL) return p;
|
||||
|
||||
// 实体这时是按 kForRead 打开的,专业数据也必须用 kForRead 取:
|
||||
// GetSpecialtyData() 的缺省参数是 kForWrite,对只读实体取写会直接崩 CAD。
|
||||
const AecDbObjXDataMap* pX = pPipe->GetSpecialtyData(AcDb::kForRead);
|
||||
if (pX != NULL)
|
||||
{
|
||||
double d = 0.0;
|
||||
if (pX->GetAt(PIPE_DN, d)) p["dn_mm"] = d * 1000.0;
|
||||
if (pX->GetAt(PIPE_OD, d)) p["od_mm"] = d * 1000.0;
|
||||
if (pX->GetAt(PIPE_THICK, d)) p["wall_thickness_mm"] = d * 1000.0;
|
||||
|
||||
CString s;
|
||||
if (pX->GetAt(PIPE_SYS, s)) p["system"] = acharToUtf8(s);
|
||||
if (pX->GetAt(PIPE_MATERIAL, s)) p["material"] = acharToUtf8(s);
|
||||
|
||||
int nClr = 0;
|
||||
if (pX->GetAt(_T("COLOR"), nClr)) p["color_index"] = nClr;
|
||||
}
|
||||
}
|
||||
else if (sType == kTypeWall)
|
||||
{
|
||||
AecDbXWall* pWall = AecDbXWall::cast(pEnt);
|
||||
WallData data;
|
||||
::memset(&data, 0, sizeof(WallData));
|
||||
if (pWall != NULL && pWall->GetWallData(data))
|
||||
{
|
||||
p["thickness"] = data.dThick;
|
||||
p["height"] = data.dH;
|
||||
p["bottom_elevation"] = data.dBomEl;
|
||||
p["align"] = (data.kAlign == kLeft) ? "left" : ((data.kAlign == kRight) ? "right" : "center");
|
||||
p["material"] = acharToUtf8(data.szMat);
|
||||
if (data.dThick2 > 1e-9)
|
||||
{
|
||||
p["thickness2"] = data.dThick2;
|
||||
p["height2"] = data.dH2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sType == kTypeSlab)
|
||||
{
|
||||
AecDbXSlab* pSlab = AecDbXSlab::cast(pEnt);
|
||||
if (pSlab == NULL) return p;
|
||||
|
||||
// 板把板厚存在 KEY_BOMTHICK,标高存在 KEY_ORGPT(见 AecDbXSlab::SetRegionSlab)
|
||||
double d = 0.0;
|
||||
if (pSlab->GetAt(KEY_BOMTHICK, d)) p["thickness"] = d;
|
||||
|
||||
AcGePoint3d ptOrg;
|
||||
if (pSlab->GetAt(KEY_ORGPT, ptOrg)) p["bottom_elevation"] = ptOrg.z;
|
||||
|
||||
CString sMat;
|
||||
if (pSlab->GetAt(KEY_MATERIAL, sMat)) p["material"] = acharToUtf8(sMat);
|
||||
}
|
||||
else if (sType == kTypeColumn)
|
||||
{
|
||||
AecDbColumn* pCol = AecDbColumn::cast(pEnt);
|
||||
if (pCol == NULL) return p;
|
||||
|
||||
p["height"] = pCol->GetHeight();
|
||||
const AcGePoint2d rc = pCol->GetRect();
|
||||
p["length"] = rc.x;
|
||||
p["width"] = rc.y;
|
||||
p["rotation_deg"] = pCol->GetRotation() * 180.0 / PI;
|
||||
}
|
||||
else if (sType == kTypeBeam)
|
||||
{
|
||||
AecDbBeam* pBeam = AecDbBeam::cast(pEnt);
|
||||
ColumnParam data;
|
||||
::memset(&data, 0, sizeof(ColumnParam));
|
||||
if (pBeam != NULL)
|
||||
{
|
||||
pBeam->GetBeamData(data);
|
||||
p["width"] = data.m_dSizes[0];
|
||||
p["height"] = data.m_dSizes[1];
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// 单个实体摘要
|
||||
nlohmann::json summaryOf(AcDbEntity* pEnt)
|
||||
{
|
||||
nlohmann::json o;
|
||||
o["handle"] = handleOf(pEnt);
|
||||
o["type"] = supportedTypeOf(pEnt);
|
||||
o["layer"] = acharToUtf8(pEnt->layer());
|
||||
|
||||
AcGePoint3d ptS, ptE;
|
||||
if (baseCurveEnds(pEnt, ptS, ptE))
|
||||
{
|
||||
o["start"] = pointJson(ptS);
|
||||
o["end"] = pointJson(ptE);
|
||||
}
|
||||
|
||||
AcGePoint3d ptMin, ptMax;
|
||||
if (entityBBox(pEnt, ptMin, ptMax))
|
||||
{
|
||||
o["bbox"] = { { "min", pointJson(ptMin) }, { "max", pointJson(ptMax) } };
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ===========================================================================
|
||||
// list_aec_entities
|
||||
// ===========================================================================
|
||||
class ToolListAecEntities : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "list_aec_entities"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "列出当前图纸里本插件支持的 Envi3D 自定义实体(管道/墙/板/柱/梁),"
|
||||
"返回 handle、类型、图层、起终点、包围盒。改图之前先用它找 handle。"
|
||||
"type 可为 pipe/wall/slab/column/beam/all,支持 limit/offset 分页。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "type": "string", "enum": [ "all", "pipe", "wall", "slab", "column", "beam" ],
|
||||
"description": "要列出的类型,缺省 all" },
|
||||
"space": { "type": "string", "description": "空间名:Model 或布局名,缺省 Model" },
|
||||
"limit": { "type": "integer", "description": "最多返回多少条,缺省 200,上限 1000" },
|
||||
"offset":{ "type": "integer", "description": "分页偏移,缺省 0" }
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
AcDbDatabase* pDb = workingDb();
|
||||
if (pDb == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
|
||||
const std::string sFilter = hasKey(args, "type") ? args["type"].get<std::string>() : "all";
|
||||
const std::string sSpace = hasKey(args, "space") ? args["space"].get<std::string>() : "Model";
|
||||
// 注意别用 std::max/std::min:windows.h 的 max/min 宏会把它顶掉
|
||||
int nLimit = intOr(args, "limit", 200);
|
||||
if (nLimit < 1) nLimit = 1;
|
||||
if (nLimit > 1000) nLimit = 1000;
|
||||
int nOffset = intOr(args, "offset", 0);
|
||||
if (nOffset < 0) nOffset = 0;
|
||||
|
||||
// 开空间
|
||||
AcDbBlockTable* pBT = NULL;
|
||||
if (pDb->getBlockTable(pBT, AcDb::kForRead) != Acad::eOk || pBT == NULL)
|
||||
return errJson(kErrInternal, L"取块表失败");
|
||||
|
||||
AcDbObjectId idSpace;
|
||||
Acad::ErrorStatus es = Acad::eKeyNotFound;
|
||||
if (sSpace.empty() || sSpace == "Model" || sSpace == "model")
|
||||
es = pBT->getAt(ACDB_MODEL_SPACE, idSpace);
|
||||
else
|
||||
es = pBT->getAt(utf8ToWstr(sSpace).c_str(), idSpace);
|
||||
pBT->close();
|
||||
|
||||
AcDbBlockTableRecord* pRec = NULL;
|
||||
if (es != Acad::eOk || acdbOpenObject(pRec, idSpace, AcDb::kForRead) != Acad::eOk || pRec == NULL)
|
||||
return errJson(kErrNotFound, L"找不到空间:" + utf8ToWstr(sSpace));
|
||||
|
||||
nlohmann::json items = nlohmann::json::array();
|
||||
int nTotal = 0, nReturned = 0;
|
||||
|
||||
AcDbBlockTableRecordIterator* pIt = NULL;
|
||||
if (pRec->newIterator(pIt) == Acad::eOk && pIt != NULL)
|
||||
{
|
||||
for (; !pIt->done(); pIt->step())
|
||||
{
|
||||
AcDbEntity* pEnt = NULL;
|
||||
if (pIt->getEntity(pEnt, AcDb::kForRead) != Acad::eOk || pEnt == NULL)
|
||||
continue;
|
||||
|
||||
const std::string sType = supportedTypeOf(pEnt);
|
||||
if (!sType.empty() && typeWanted(sType, sFilter))
|
||||
{
|
||||
++nTotal;
|
||||
if (nTotal > nOffset && nReturned < nLimit)
|
||||
{
|
||||
items.push_back(summaryOf(pEnt));
|
||||
++nReturned;
|
||||
}
|
||||
}
|
||||
pEnt->close();
|
||||
}
|
||||
delete pIt;
|
||||
}
|
||||
pRec->close();
|
||||
|
||||
nlohmann::json body;
|
||||
body["total"] = nTotal;
|
||||
body["returned"] = nReturned;
|
||||
body["offset"] = nOffset;
|
||||
body["items"] = items;
|
||||
return okJson(body);
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolListAecEntities, ListAecEntitiesTool)
|
||||
|
||||
// ===========================================================================
|
||||
// get_aec_entity
|
||||
// ===========================================================================
|
||||
class ToolGetAecEntity : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
std::string private_name() const override { return "get_aec_entity"; }
|
||||
|
||||
std::string private_description() const override
|
||||
{
|
||||
return "按 handle 取单个 Envi3D 实体的详细参数(类型、图层、起终点、包围盒,"
|
||||
"以及该类型的关键尺寸:管道的 DN/外径/壁厚、墙的厚高标高、板厚、柱截面、梁截面)。";
|
||||
}
|
||||
|
||||
nlohmann::json private_input_schema() const override
|
||||
{
|
||||
return nlohmann::json::parse(R"({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handle": { "type": "string", "description": "实体句柄(十六进制字符串)" }
|
||||
},
|
||||
"required": [ "handle" ]
|
||||
})");
|
||||
}
|
||||
|
||||
nlohmann::json Execute(const nlohmann::json& args) override
|
||||
{
|
||||
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
|
||||
if (!hasKey(args, "handle")) return errJson(kErrBadParam, L"缺少 handle");
|
||||
|
||||
const std::string sHandle = args["handle"].get<std::string>();
|
||||
AcDbEntity* pEnt = openByHandle(sHandle, AcDb::kForRead);
|
||||
if (pEnt == NULL)
|
||||
return errJson(kErrNotFound, L"按 handle 打不开实体:" + utf8ToWstr(sHandle));
|
||||
|
||||
const std::string sType = supportedTypeOf(pEnt);
|
||||
if (sType.empty())
|
||||
{
|
||||
const std::string sCls = classNameOf(pEnt);
|
||||
pEnt->close();
|
||||
return errJson(kErrWrongType,
|
||||
L"实体 " + utf8ToWstr(sCls) + L" 不在本插件支持的范围内"
|
||||
L"(只支持管道/墙/板/柱/梁)");
|
||||
}
|
||||
|
||||
nlohmann::json body = summaryOf(pEnt);
|
||||
body["params"] = paramsOf(pEnt, sType);
|
||||
pEnt->close();
|
||||
|
||||
return okJson(body);
|
||||
}
|
||||
};
|
||||
EXPORT_MCP_TOOL(ToolGetAecEntity, GetAecEntityTool)
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"_comment": [
|
||||
"cad_mcp_aec 的工具描述。合到部署目录的 cad-mcp-frame.json 的 tools 数组里即可用(append)。",
|
||||
"没有写 inputSchema:插件里的 mcp_Tool::private_input_schema() 已经带了完整 schema,",
|
||||
"配置里留空时框架会自动回落到它(见 inc/mcp_plugin_api.h 的 ToLLM())。",
|
||||
"dll_name 与 cad_mcp_frame.arx 同目录(plugin_directory = \".\")。"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "list_aec_entities",
|
||||
"description": "列出当前图纸里本插件支持的 Envi3D 自定义实体(管道/墙/板/柱/梁),返回 handle、类型、图层、起终点、包围盒。改图之前先用它拿 handle。type 可选 all/pipe/wall/slab/column/beam,limit/offset 分页。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "ListAecEntitiesTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_aec_entity",
|
||||
"description": "按 handle 取单个 Envi3D 实体的详细参数:类型、图层、起终点、包围盒,以及该类型的关键尺寸(管道 DN/外径/壁厚;墙 厚/高/底标高/对齐;板 厚/底标高;柱 高/截面/转角;梁 截面)。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "GetAecEntityTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_aec_pipe",
|
||||
"description": "新建 Envi3D 管道(AecDbEwPipe):给起点、终点和公称直径,可选外径/壁厚/系统/材质/颜色/图层。坐标用图纸单位,dn_mm/od_mm/wall_thickness_mm 用毫米。返回新实体的 handle。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "CreateAecPipeTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_aec_pipe",
|
||||
"description": "修改已有管道:只传要改的字段(起终点、DN、外径、壁厚、系统、材质、颜色),未传的保持原值。改 DN 会重建截面,改起终点会重算几何。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "UpdateAecPipeTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_aec_wall",
|
||||
"description": "新建 Envi3D 墙体(AecDbXWall):沿 start→end 建墙,给墙厚、墙高、墙底标高,align 控制墙厚在基线哪一侧(left/center/right)。尺寸用图纸单位。返回 handle。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "CreateAecWallTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_aec_wall",
|
||||
"description": "修改已有墙体:只传要改的字段(起终点、墙厚、墙高、底标高、对齐、材质)。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "UpdateAecWallTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_aec_slab",
|
||||
"description": "新建 Envi3D 板/底板(AecDbXSlab):points 是水平轮廓点串 [[x,y],...](至少 3 点,可不闭合),给板厚与板底标高(图纸单位),center 可指定位置参考点。返回 handle。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "CreateAecSlabTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_aec_slab",
|
||||
"description": "修改已有板:传 points 会重做轮廓,否则只改板厚/板底标高/材质。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "UpdateAecSlabTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_aec_column",
|
||||
"description": "新建矩形柱(AecDbColumn):position 是柱底中心点,给柱高、截面长、截面宽,可选绕 Z 轴转角(度)。尺寸用图纸单位。返回 handle。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "CreateAecColumnTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_aec_column",
|
||||
"description": "修改已有柱:只传要改的字段(位置、柱高、截面长宽、转角、材质)。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "UpdateAecColumnTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_aec_beam",
|
||||
"description": "新建梁(AecDbBeam):沿 start→end 建梁,给截面宽与高(图纸单位)。返回 handle。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "CreateAecBeamTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_aec_beam",
|
||||
"description": "修改已有梁:只传要改的字段(起终点、截面宽高、材质)。",
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_aec.dll",
|
||||
"factory_function": "UpdateAecBeamTool"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(CadMcpAec)
|
||||
|
||||
set(SOURCES
|
||||
"mcp_aec.cpp"
|
||||
"aec_mcp_common.cpp"
|
||||
"aec_mcp_entity_tools.cpp"
|
||||
"aec_mcp_query.cpp"
|
||||
"StdAfx.cpp"
|
||||
)
|
||||
|
||||
add_library(CadMcpAec SHARED ${SOURCES})
|
||||
|
||||
target_precompile_headers(CadMcpAec PRIVATE StdAfx.h)
|
||||
|
||||
target_set_name_and_suffix(CadMcpAec "cad_mcp_aec" ".dll")
|
||||
|
||||
target_link_objectarx(CadMcpAec CADMCPAEC_EXPORT)
|
||||
|
||||
# Unlike cad_mcp_plugins (read-only AcDb review, no CoreCommand link), this plugin
|
||||
# must new/modify the repo's custom entities (AecDbEwPipe, AecDbXWall, ...).
|
||||
# Their implementation lives in CoreCommand.arx (which contains the static
|
||||
# CoreLibrary), so link it - same as PipelineCmd does.
|
||||
#
|
||||
# Do NOT define EARTHWORM_EXPORT / AECRXBASE_EXPORT here: those macros are for
|
||||
# CoreLibrary/AecRxBase themselves when they export these classes; we import
|
||||
# them, so keep the default dllimport from the headers.
|
||||
target_link_libraries(CadMcpAec PRIVATE
|
||||
AecRxBase
|
||||
CoreCommand_BAL_REMOTE
|
||||
)
|
||||
|
||||
|
||||
if (IS_ZWCAD)
|
||||
target_compile_definitions(CadMcpAec PRIVATE SUB_ARX=0)
|
||||
else()
|
||||
target_compile_definitions(CadMcpAec PRIVATE SUB_ARX=${CAD_SDK_SUBVERSION})
|
||||
endif()
|
||||
|
||||
set_debug_post_build(CadMcpAec "cad_mcp_aec" ".dll")
|
||||
@@ -0,0 +1,26 @@
|
||||
// ============================================================================
|
||||
// mcp_aec.cpp
|
||||
// cad_mcp_aec 插件入口
|
||||
//
|
||||
// 与 cad_mcp_plugins 的区别只在 CMake:本插件链接 CoreCommand.arx,
|
||||
// 因此能 new / 改仓库的自定义实体(AecDbEwPipe / AecDbXWall / AecDbXSlab /
|
||||
// AecDbColumn / AecDbBeam)。工具本身仍由 cad_mcp_frame 在运行时 LoadLibrary。
|
||||
// ============================================================================
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include "KTCad.h"
|
||||
#include "mcp_plugin_api.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#ifdef ZWCAD
|
||||
// 中望的库是 ZwRx.lib,符号也换成了 zcrx 家族(acrxEntryPoint 会被 SDK 宏改名,
|
||||
// 但显式写出来的 GetApiVersion 不会,所以这里按平台分开)。
|
||||
#pragma comment(linker, "/export:zcrxGetApiVersion,PRIVATE")
|
||||
#else
|
||||
#pragma comment(lib, "rxapi.lib")
|
||||
#pragma comment(linker, "/export:acrxGetApiVersion,PRIVATE")
|
||||
#endif
|
||||
|
||||
// 把本 DLL 编译时的 SDK 版本写成数据符号:基座读内存比对,不涉及调用约定
|
||||
EXPORT_MCP_PLUGIN_VERSION()
|
||||
@@ -131,6 +131,7 @@ add_custom_module(SPLDCmd "SH/Structures")
|
||||
if (IS_ZWCAD OR CAD_SDK_VERSION GREATER_EQUAL 20)
|
||||
add_custom_module_2(cad_mcp_frame CadMcpFrame "SH/Other")
|
||||
add_custom_module_2(cad_mcp_plugins CadMcpPlugins "SH/Other")
|
||||
add_custom_module_2(cad_mcp_aec CadMcpAec "SH/Other")
|
||||
else()
|
||||
message(STATUS ">>> 跳过 cad_mcp_frame:需要 C++11+(当前 preset 为 C++98)")
|
||||
endif()
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
# cad_mcp_aec —— 让 agent 直接建/改 Envi3D 自定义实体
|
||||
|
||||
> 工作计划第 3 项。给 `cad_mcp_frame` 增加一个**能访问仓库自定义实体**的插件工程。
|
||||
> 现有 `cad_mcp_plugins` 只做 AcDb 层面的只读审查(它不链接 CoreCommand),
|
||||
> 碰不到 `AecDbEwPipe` / `AecDbXWall` / `AecDbXSlab` / `AecDbColumn` / `AecDbBeam`。
|
||||
> 新工程 `cad_mcp_aec` 链接 CoreCommand(内含静态库 CoreLibrary),因此可以 `new` 和修改这些实体。
|
||||
|
||||
## 1. 工程结构
|
||||
|
||||
```
|
||||
SourceCode/Code2026/cad_mcp_aec/
|
||||
├── cmakelists.txt 按 PipelineCmd 的写法;输出普通 DLL(不是 .arx)
|
||||
├── StdAfx.h / StdAfx.cpp 与 cad_mcp_plugins 同款 MFC 预处理头
|
||||
├── mcp_aec.cpp 入口:EXPORT_MCP_PLUGIN_VERSION()(导出 SDK 版本数据符号)
|
||||
├── aec_mcp_common.h/.cpp 参数解析、handle<->ObjectId、文档锁、结果信封
|
||||
├── aec_mcp_entity_tools.cpp 5 个 create_* + 5 个 update_*
|
||||
├── aec_mcp_query.cpp list_aec_entities / get_aec_entity
|
||||
└── cad-mcp-aec.json 工具描述(合到运行期 cad-mcp-frame.json 的 tools 数组里)
|
||||
```
|
||||
|
||||
顶层 `cmakelists.txt` 里挂在 `cad_mcp_frame` 同一处:
|
||||
|
||||
```cmake
|
||||
if (IS_ZWCAD OR CAD_SDK_VERSION GREATER_EQUAL 20)
|
||||
add_custom_module_2(cad_mcp_frame CadMcpFrame "SH/Other")
|
||||
add_custom_module_2(cad_mcp_plugins CadMcpPlugins "SH/Other")
|
||||
add_custom_module_2(cad_mcp_aec CadMcpAec "SH/Other")
|
||||
```
|
||||
|
||||
和 `cad_mcp_frame` / `cad_mcp_plugins` 一样受 **C++11+** 限制:R180/R190 是 C++98,会跳过。
|
||||
|
||||
## 2. 两个关键的 CMake 细节
|
||||
|
||||
1. **链接 CoreCommand**(与 PipelineCmd 相同):
|
||||
|
||||
```cmake
|
||||
target_link_libraries(CadMcpAec PRIVATE AecRxBase CoreCommand_BAL_REMOTE)
|
||||
```
|
||||
|
||||
自定义实体的实现都在 `CoreCommand.arx`(它内含静态库 `CoreLibrary`)。
|
||||
|
||||
2. **不要**定义 `EARTHWORM_EXPORT` / `AECRXBASE_EXPORT`。
|
||||
这两个宏是 CoreLibrary / AecRxBase **自己导出**时用的(见 `CoreLibrary/cmakelists.txt`),
|
||||
插件的头文件里必须保持默认的 `dllimport`。`cad_mcp_plugins` 里定义了它们,
|
||||
那是因为它不使用这些类;本插件用了,定义了就变成"导出自己没有的符号"。
|
||||
|
||||
## 3. 工具清单(12 个)
|
||||
|
||||
| 工具 | 作用 | 关键参数 |
|
||||
| --- | --- | --- |
|
||||
| `list_aec_entities` | 列出管道/墙/板/柱/梁(handle、类型、图层、起终点、包围盒) | `type`(all/pipe/wall/slab/column/beam)、`space`、`limit`、`offset` |
|
||||
| `get_aec_entity` | 按 handle 取详细参数 | `handle` |
|
||||
| `create_aec_pipe` / `update_aec_pipe` | 建/改管道(`AecDbEwPipe`) | `start`/`end`、`dn_mm`、`od_mm`、`wall_thickness_mm`、`system`、`material`、`color_index`、`layer` |
|
||||
| `create_aec_wall` / `update_aec_wall` | 建/改墙(`AecDbXWall`) | `start`/`end`、`thickness`、`height`、`bottom_elevation`、`align`(left/center/right) |
|
||||
| `create_aec_slab` / `update_aec_slab` | 建/改板(`AecDbXSlab`) | `points`([[x,y],...])、`thickness`、`bottom_elevation`、`center` |
|
||||
| `create_aec_column` / `update_aec_column` | 建/改柱(`AecDbColumn`) | `position`、`height`、`length`、`width`、`rotation_deg` |
|
||||
| `create_aec_beam` / `update_aec_beam` | 建/改梁(`AecDbBeam`) | `start`/`end`、`width`、`height` |
|
||||
|
||||
**单位约定**(写进了每个工具的 description):
|
||||
|
||||
- 坐标与建筑尺寸(墙厚/墙高/板厚/柱高/梁宽…)用**图纸单位**;
|
||||
- 管道的 `dn_mm` / `od_mm` / `wall_thickness_mm` 用 **mm**,内部按 `AS_MUNIT` 转米,
|
||||
与仓库里所有管道对话框(`BZGDDlg`、`AecPipeLineTool`)一致。
|
||||
|
||||
**返回值**:统一 `{"ok":true, "type":..., "handle":...}`;错误是 MCP 的 error 信道
|
||||
(`-32001` 参数非法 / `-32002` 找不到 / `-32004` 类型不对 / `-32603` 内部错误)。
|
||||
|
||||
**update_\*** 只改传入的字段,其余保持原值;管道只给一个端点时,另一端沿用实体现有端点。
|
||||
|
||||
## 4. 三个实现上的坑(都是实测撞出来的)
|
||||
|
||||
### 4.1 写数据库必须先拿文档锁
|
||||
|
||||
MCP 工具是通过 `PostMessage` 在主线程的消息里跑的,**不在命令上下文**中,
|
||||
直接 `appendAcDbEntity` 会拿到 `Acad::eLockViolation`(=0x140=320)。
|
||||
`aec_mcp_common.h` 里的 `DocLock` 负责 `acDocManager->lockDocument()` / `unlockDocument()`。
|
||||
|
||||
### 4.2 只读实体上取专业数据必须 `kForRead`
|
||||
|
||||
`GetSpecialtyData()` 的**缺省参数是 `AcDb::kForWrite`**。
|
||||
`get_aec_entity` 里实体是按 `kForRead` 打开的,第一次实现没注意,**直接把 acad 崩了**
|
||||
(`%TEMP%\acminidump.dmp`)。现在统一写 `GetSpecialtyData(AcDb::kForRead)`。
|
||||
`AecDbEwPipeBase.h` 的注释里也写着同样的话("在 draw2/3d 中使用必须 kForRead,否则图不能保存")。
|
||||
|
||||
### 4.3 `SetSection()` 会清掉派生字段,`SetRegionSlab()` 不自动闭合
|
||||
|
||||
- `AecDbEwPipeBase::SetSection(dD, dt)` 重建截面时会重置 `PIPE_THICK` 等专业数据。
|
||||
所以 `update_aec_pipe` 先把现有的 DN/OD/壁厚读出来,合并后再统一写回,
|
||||
否则"只改 DN"会把壁厚清成 0(这是实测发现的,已修)。
|
||||
- `AecDbXSlab::SetRegionSlab(AcGePoint2dArray&, ...)` 内部用
|
||||
`CreatePolygonRegionOnly()` 连线,而后者只连 `[0, n-2]` 段、**不补闭合点**
|
||||
(闭合点只写进了 `KEY_POINTS` 字符串)。所以传点串时必须自己把首点补到末尾,
|
||||
否则 region 建不出来、直接返回 false。插件里已补。
|
||||
- `update_aec_slab` 不带 `points` 时要改板厚/标高:把存盘的 `KEY_POINTS` 串取出来,
|
||||
走 `SetRegionSlab(LPCTSTR, ...)` 字符串重载(数组重载必须重新给轮廓)。
|
||||
|
||||
## 5. 端到端实测(AutoCAD 2020 / R230 Debug)
|
||||
|
||||
做法:临时把 `cad-mcp-aec.json` 的 12 个工具合进部署目录的 `cad-mcp-frame.json`
|
||||
(备份原文件,测完还原),用 `/b` 脚本加载 `cad_mcp_frame.arx`,然后用裸 socket 打 JSON-RPC。
|
||||
|
||||
| 用例 | 结果 |
|
||||
| --- | --- |
|
||||
| `tools/list` | ✅ 返回 **30** 个工具(原 18 + 新增 12),`create_aec_pipe` 等描述可见 |
|
||||
| `list_aec_entities` | ✅ pcf_1.dwg 里列出 20 根管道,handle/图层/起终点/包围盒正确 |
|
||||
| `create_aec_pipe` | ✅ 返回 handle `3A0`,起点(0,0,0)→终点(2000,0,0) |
|
||||
| `get_aec_entity` | ✅ 读回 `dn_mm=100 / od_mm=114 / wall_thickness_mm=4` |
|
||||
| `update_aec_pipe` | ✅ 改 `dn_mm=150` + 只给 `end`:终点变 3000,**壁厚仍是 4**(4.3 的修复生效) |
|
||||
| `create_aec_wall` | ✅ `3A1`;get 读回 thickness=200 / height=3000 / align=center |
|
||||
| `create_aec_column` | ✅ `3A2`;get 读回 height=3000 / length=400 / width=400 |
|
||||
| `create_aec_beam` | ✅ `3A3`;get 读回 width=300 / height=500 |
|
||||
| `create_aec_slab` | ✅ `3A0`;get 读回 thickness=100 / bottom_elevation=-500 / material=钢筋混凝土 |
|
||||
| `update_aec_slab`(只改厚度/标高) | ✅ 150 / -300 都生效 |
|
||||
| 类型不匹配 | ✅ `update_aec_wall` 传板的 handle → `-32004`「实体是 XSlab,不是 AecDbXWall」 |
|
||||
| 不存在的 handle | ✅ `-32002` |
|
||||
|
||||
导出表(`dumpbin -exports cad_mcp_aec.dll`):12 个 `Create*/Update*` 工厂 + `Destroy_*`
|
||||
+ `g_mcpPluginApiVersion` 全部在。
|
||||
|
||||
## 6. 部署与启用
|
||||
|
||||
1. 编译:`cmake --build --preset R230 --config Debug --target CadMcpAec`
|
||||
→ `cad_mcp_aec.dll` 随 Debug 构建自动部署到 `c:\ShangHuaSoft\Envi3D-2026-M\R230\`
|
||||
(与 `cad_mcp_frame.arx` 同目录,`plugin_directory` 是 `"."`)。
|
||||
2. 把 `cad-mcp-aec.json` 里的 `tools` 追加到部署目录的 `cad-mcp-frame.json`:
|
||||
|
||||
```python
|
||||
python - <<'EOF'
|
||||
import json
|
||||
cfg = json.load(open('cad-mcp-frame.json', encoding='utf-8'))
|
||||
add = json.load(open('cad-mcp-aec.json', encoding='utf-8'))
|
||||
names = {t['name'] for t in cfg['tools']}
|
||||
cfg['tools'] += [t for t in add['tools'] if t['name'] not in names]
|
||||
json.dump(cfg, open('cad-mcp-frame.json','w',encoding='utf-8'), ensure_ascii=False, indent='\t')
|
||||
EOF
|
||||
```
|
||||
|
||||
`cad-mcp-aec.json` 里**故意不写 `inputSchema`**:插件里
|
||||
`mcp_Tool::private_input_schema()` 已经带了完整 schema,配置留空时框架会自动回落
|
||||
(`inc/mcp_plugin_api.h` 的 `ToLLM()`)。这样描述与代码不会漂移。
|
||||
3. 重启 CAD(或 `/reload`),`tools/list` 里应能看到 12 个 `*_aec_*` 工具。
|
||||
|
||||
## 7. 已知限制
|
||||
|
||||
- 只支持这 5 类实体;其它 `AecDb*` 返回 `-32004`。要扩就照 `aec_mcp_entity_tools.cpp`
|
||||
里"一个 apply 函数 + create/update 两个壳"的写法加。
|
||||
- 新建实体后 `bbox` 会是 `±1e20`:自定义实体的几何要等一次 REGEN 才建出来,
|
||||
`getGeomExtents` 在未绘制时返回无效值。不是错误。
|
||||
- 没有事务/撤销包装:调用即改库。批量改图建议先 `list` 拿 handle,再逐条 `update`。
|
||||
- 工具描述的中文来自代码里的 `private_description()`;如果最终以运行期 JSON 为准,
|
||||
记得两边同步(或者干脆像现在这样只在 JSON 里写 name + description + backend)。
|
||||
Reference in new issue
Block a user