第 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 条。
360 lines
11 KiB
C++
360 lines
11 KiB
C++
// ============================================================================
|
||
// 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)
|