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