// ============================================================================ // 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 "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"; // 实体 -> 五个受支持类型之一;不是这几类返回空串 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; 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 == 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" ], "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() : "all"; const std::string sSpace = hasKey(args, "space") ? args["space"].get() : "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(); 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)