Files
envi-code/SourceCode/Code2026/cad_mcp_aec/aec_mcp_entity_tools.cpp
T
company-desktop 4f1ea72e4e refactor: cad_mcp_aec 工具改按仓库既有格式描述,并合入 mcp_config.json
现象:上一版自己发明的描述文件 cad-mcp-aec.json 与仓库既有工具(add_line /
query_entities / get_entity_detail)的风格不一致——工具名前缀不同、
点用 {x,y,z} 对象而不是 [x,y] 数组、颜色叫 color_index 而不是 color。
另外 cad_mcp_frame 的运行期配置文件名是 mcp_config.json(找不到才回落
cad-mcp-frame.json),单个 cad-mcp-aec.json 还得手工合并。

改动:
1. 工具改名,与既有工具同一套动词_名词风格:
   list_aec_entities        -> query_aec_entities
   get_aec_entity           -> get_aec_entity_detail
   create_aec_pipe|wall|slab|column|beam -> add_aec_*
2. 参数约定对齐既有工具:
   - 点改用 [x,y] / [x,y,z] 数组(对象写法仍兼容,后端不变);
   - 颜色用 color(ACI,256=ByLayer),并兼容旧写法 color_index;
   - add_* 支持 space(Model 或布局名),缺省模型空间;
   - get_aec_entity_detail 的输出也从 color_index 改成 color,和入参对称。
3. 删掉自定义的 cad-mcp-aec.json,把 12 个工具按标准 MCP 字段 + backend
   (type/dll_name/factory_function)写进 cad_mcp_aec/mcp_config.json,
   与既有 28 个工具同一份文件;inputSchema 写全,带每个参数的 description。
4. 插件里的 private_name()/private_input_schema() 同步成同一套名字与 schema,
   保证配置里没写 schema 时回落也一致。

验证:
- R230 Debug / Z2026 Debug 均编译通过并部署。
- AutoCAD 2020 实测(部署目录放这份 mcp_config.json,cad_mcp_frame 优先读它):
  tools/list = 30(既有 18 + 本插件 12),12 个 add_aec_*/update_aec_* 工厂全部加载成功;
  query 列出 20 根管道;add_aec_pipe 用 [0,0,0]->[2000,0,0] + color=1 建成功,
  detail 读回 dn_mm=100/od_mm=114/wall_thickness_mm=4/color=1;
  只给 end 的 update 后端点变 2500、壁厚与颜色不变;
  add_aec_wall/slab/column/beam 各建一次并读回参数一致。

已知(与本次改动无关):mcp_config.json 里有 10 个工具在仓库当前的
cad_mcp_plugins.dll 里没有对应工厂函数(AddText/AddMText/AddLine/AddPolyline/
AddLeaderAnnotation/DeleteEntities/SetEntityProperties/DrawRevisionCloud/
FindCollisions/SetDrawingArea),会被静默丢掉,补齐需要先把那些工具的实现
补进 cad_mcp_plugins。
2026-10-29 22:26:34 +08:00

932 lines
31 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================================
// 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"add_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"add_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());
// 颜色:与 cad_mcp_plugins 的既有工具统一用 color(ACI,256=ByLayer),
// 同时兼容旧写法 color_index
if (hasKey(a, "color"))
pX->SetAt(_T("COLOR"), intOr(a, "color", 256));
else if (hasKey(a, "color_index"))
pX->SetAt(_T("COLOR"), intOr(a, "color_index", 256));
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"add_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"add_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"add_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"add_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"add_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);
}
}
// space:与 cad_mcp_plugins 的 add_* 工具一致,缺省模型空间
std::wstring sSpace;
if (hasKey(a, "space"))
sSpace = wstrOr(a, "space", L"");
AcDbObjectId idNew;
Acad::ErrorStatus es = addToSpace(pEnt, &idNew, sSpace);
// 句柄要**入图之后**才有值: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 "add_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": { "type": "integer", "description": "ACI 颜色号,256=ByLayer" },
"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": { "type": "integer", "description": "ACI 颜色号,256=ByLayer(可选)" }
},
"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 "add_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 "add_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 "add_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 "add_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)