Files
envi-code/SourceCode/Code2026/cad_mcp_aec/aec_mcp_query.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

360 lines
11 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_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 "AecDbClimbStair.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";
const char* kTypeClimb = "climb_stair";
// 实体 -> 五个受支持类型之一;不是这几类返回空串
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;
if (AecDbClimbStair::cast(pEnt)) return kTypeClimb;
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);
// 与入参统一用 color(ACI);老字段名 color_index 不再输出
int nClr = 0;
if (pX->GetAt(_T("COLOR"), nClr)) p["color"] = 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 == kTypeClimb)
{
AecDbClimbStair* pStair = AecDbClimbStair::cast(pEnt);
ClimbStair data;
::memset(&data, 0, sizeof(ClimbStair));
if (pStair != NULL)
{
pStair->GetClimbStairData(data);
p["type"] = data.nType;
p["cage_diameter"] = data.dD;
p["width"] = data.dW;
p["height"] = data.dH;
p["bottom_elevation"] = data.dBomEl;
p["step_height"] = data.dSteepH;
p["step_spacing"] = data.dSegSp;
p["wall_distance"] = data.dSpWall;
p["bar_diameter"] = data.dd;
p["material"] = acharToUtf8(data.m_sMaterial);
}
}
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 "query_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", "climb_stair" ],
"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_detail"; }
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)