Files
envi-code/SourceCode/Code2026/cad_mcp_aec/aec_mcp_entity_tools.cpp
T
company-desktop 266a9f5e4c feat: cad_mcp_aec 增加爬梯工具,query 支持 climb_stair
第 4 项(按 sys/*.mnu 扩工具)的第一步:从菜单命令里筛出一类参数清晰的实体先做通。

- 新增 add_aec_climb_stair / update_aec_climb_stair(AecDbClimbStair,对应菜单命令
  PL_ZXPT 直爬梯、PL_YHZT 圆护爬梯)。建模序列照抄 pool3dcmdCmd.cpp:5742 的命令实现:
  SetSection(踏管直径) -> SetBaseCurve(底->顶) -> SetClimbStairData(参数体),
  再写专业数据 2dStyle / 朝向(KEY_MAJBR)。工具直接收两个端点,不需要复现
  "放置点+朝向"那套偏移算法。
  参数:start/end、width、cage_diameter、height、bottom_elevation、type(0/1)、
  bar_diameter、step_height、step_spacing、wall_distance、facing、material、layer。
- query_aec_entities / get_aec_entity_detail 支持 climb_stair(读 GetClimbStairData)。
- mcp_config.json 增加这两个工具(40 -> 42),query 的 type 枚举补 climb_stair。

实测(AutoCAD 2020 / R230 Debug):
- tools/list = 42;add_aec_climb_stair 建出 handle 3A0(宽 450 / 护笼 700 / 高 3000);
- get_aec_entity_detail 读回 type=0/width=450/cage=700/height=3000/bar=20/step=300;
- update 改 width=600、step_height=250 后读回一致;query type=climb_stair 命中 1 条。
2026-10-29 23:56:58 +08:00

1142 lines
39 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 "AecDbClimbStair.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)
// ===========================================================================
// 爬梯(AecDbClimbStair)
// 对应菜单命令 PL_ZXPT 直爬梯 / PL_YHZT 圆护爬梯(pool3dcmdCmd.cpp:5742)。
// 建模序列照抄命令实现:setLayer -> SetSection(踏管直径) -> SetBaseCurve(底->顶)
// -> SetClimbStairData(参数体) -> 专业数据 2dStyle/朝向。
// ===========================================================================
namespace
{
// 爬梯参数体:没传的字段用"现有值",新建时用命令对话框里的缺省量级
bool ApplyClimbStair(AecDbClimbStair* 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_climb_stair 需要 start(梯段底部点) 与 end(顶部点)";
return false;
}
ClimbStair data;
::memset(&data, 0, sizeof(ClimbStair));
p->GetClimbStairData(data);
if (bCreate)
{
data.nType = hasKey(a, "type") ? intOr(a, "type", 0) : 0; // 0 圆护直梯 1 圆护凸爬
data.dSegSp = 300.0;
data.dSteepH = 300.0;
data.dSpWall = 180.0;
data.dD = 700.0;
data.dW = 450.0;
data.dd = 20.0;
data.dt = 2.5;
}
if (hasKey(a, "type")) data.nType = intOr(a, "type", data.nType);
if (hasKey(a, "cage_diameter")) data.dD = numOr(a, "cage_diameter", data.dD);
if (hasKey(a, "width")) data.dW = numOr(a, "width", data.dW);
if (hasKey(a, "height")) data.dH = numOr(a, "height", data.dH);
if (hasKey(a, "bottom_elevation")) data.dBomEl = numOr(a, "bottom_elevation", data.dBomEl);
if (hasKey(a, "step_height")) data.dSteepH = numOr(a, "step_height", data.dSteepH);
if (hasKey(a, "step_spacing")) data.dSegSp = numOr(a, "step_spacing", data.dSegSp);
if (hasKey(a, "wall_distance")) data.dSpWall = numOr(a, "wall_distance", data.dSpWall);
if (hasKey(a, "bar_diameter")) data.dd = numOr(a, "bar_diameter", data.dd);
if (hasKey(a, "bar_thickness")) data.dt = numOr(a, "bar_thickness", data.dt);
if (hasKey(a, "material"))
{
CString sMat(wstrOr(a, "material", L"").c_str());
_tcsncpy_s(data.m_sMaterial, 256, sMat, _TRUNCATE);
}
if (data.dW <= 0.0 || data.dD <= 0.0)
{
err = L"爬梯需要 width 与 cage_diameter,且都 > 0";
return false;
}
// 梯段基线:命令里是按"放置点+朝向"算出来的,工具直接收两个端点(更直接)
if (bHasS && bHasE)
{
if (ptS.distanceTo(ptE) < 1e-9)
{
err = L"start 与 end 重合,梯段长度为 0";
return false;
}
if (data.dH <= 1e-9)
data.dH = ptE.z - ptS.z;
if (data.dH <= 1e-9)
data.dH = 3000.0;
p->SetSection(data.dd, 0.0);
p->SetBaseCurve(ptS, ptE);
}
p->SetClimbStairData(data);
// 朝向:命令里存的是"人面对的方向",缺省给 +Y
AcGeVector3d vtFace(0.0, 1.0, 0.0);
{
nlohmann::json jFace;
if (a.is_object() && a.contains("facing"))
{
AcGePoint3d ptFace;
if (readPoint(a, "facing", ptFace))
vtFace = ptFace.asVector();
}
vtFace.normalize();
}
p->GetSpecialtyData()->SetAt(KEY_2DSTYLE, 0);
p->GetSpecialtyData()->SetAt(KEY_MAJBR, vtFace);
return true;
}
}
class ToolAddClimbStair : public mcp_Tool
{
public:
std::string private_name() const override { return "add_aec_climb_stair"; }
std::string private_description() const override
{
return "新建爬梯(AecDbClimbStair,对应菜单命令 直爬梯/圆护爬梯)。"
"给梯段底部点与顶部点,以及梯宽、护笼直径、总高;其余按对话框缺省。尺寸用图纸单位。";
}
nlohmann::json private_input_schema() const override
{
return nlohmann::json::parse(R"({
"type": "object",
"properties": {
"start": { "type": "array", "items": { "type": "number" }, "description": "梯段底部点 [x,y] 或 [x,y,z]" },
"end": { "type": "array", "items": { "type": "number" }, "description": "梯段顶部点" },
"width": { "type": "number", "description": "梯宽,图纸单位(缺省 450)" },
"cage_diameter": { "type": "number", "description": "护笼总直径(缺省 700)" },
"height": { "type": "number", "description": "总高,不给则用 end.z - start.z" },
"bottom_elevation": { "type": "number", "description": "底标高,缺省 0" },
"type": { "type": "integer", "description": "0=圆护直梯(缺省)1=圆护凸爬" },
"bar_diameter": { "type": "number", "description": "踏管直径(缺省 20)" },
"step_height": { "type": "number", "description": "踏步高(缺省 300)" },
"step_spacing": { "type": "number", "description": "踏棍间距(缺省 300)" },
"wall_distance": { "type": "number", "description": "距墙距离(缺省 180)" },
"facing": { "type": "array", "items": { "type": "number" }, "description": "人面对的方向 [x,y],缺省 +Y" },
"material": { "type": "string", "description": "材质" },
"layer": { "type": "string", "description": "图层,缺省 LAY_CSTAIR" }
},
"required": [ "start", "end" ]
})");
}
nlohmann::json Execute(const nlohmann::json& args) override
{
if (workingDb() == NULL) return errJson(kErrNoDocument, L"没有活动图纸");
DocLock lock;
if (!lock.isLocked()) return errJson(kErrInternal, L"拿不到图纸写锁(有别的命令在跑?)");
AecDbClimbStair* pStair = new AecDbClimbStair();
std::wstring err;
if (!ApplyClimbStair(pStair, args, true, err))
{
delete pStair;
return errJson(kErrBadParam, err);
}
// 命令里固定用 LAY_CSTAIR 图层;工具允许覆盖
if (!hasKey(args, "layer"))
{
AcDbObjectId idLay = layerIdByName(L"LAY_CSTAIR");
if (!idLay.isNull())
pStair->setLayer(idLay);
}
return finishCreate(pStair, args, "climb_stair");
}
};
EXPORT_MCP_TOOL(ToolAddClimbStair, AddAecClimbStairTool)
class ToolUpdateClimbStair : public mcp_Tool
{
public:
std::string private_name() const override { return "update_aec_climb_stair"; }
std::string private_description() const override
{
return "修改已有爬梯:只传要改的字段(端点、梯宽、护笼直径、总高、踏步、材质…)。";
}
nlohmann::json private_input_schema() const override
{
return nlohmann::json::parse(R"({
"type": "object",
"properties": {
"handle": { "type": "string", "description": "爬梯句柄" },
"start": { "type": "array", "items": { "type": "number" }, "description": "新起点(可选)" },
"end": { "type": "array", "items": { "type": "number" }, "description": "新终点(可选)" },
"width": { "type": "number" }, "cage_diameter": { "type": "number" },
"height": { "type": "number" }, "bottom_elevation": { "type": "number" },
"type": { "type": "integer" }, "bar_diameter": { "type": "number" },
"step_height": { "type": "number" }, "step_spacing": { "type": "number" },
"wall_distance": { "type": "number" }, "facing": { "type": "array", "items": { "type": "number" } },
"material": { "type": "string" }
},
"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;
AecDbClimbStair* pStair = openTyped<AecDbClimbStair>(args, L"AecDbClimbStair", err);
if (pStair == NULL) return err;
std::wstring msg;
if (!ApplyClimbStair(pStair, args, false, msg))
{
pStair->close();
return errJson(kErrBadParam, msg);
}
return finishUpdate(pStair, args["handle"].get<std::string>(), "climb_stair");
}
};
EXPORT_MCP_TOOL(ToolUpdateClimbStair, UpdateAecClimbStairTool)