From 25e09eec586112a2f040928ea0a55e15846dbee0 Mon Sep 17 00:00:00 2001 From: company-desktop-computer Date: Thu, 29 Oct 2026 23:56:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20cad=5Fmcp=5Faec=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=88=AC=E6=A2=AF=E5=B7=A5=E5=85=B7=EF=BC=8Cquery=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20climb=5Fstair?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第 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 条。 --- .../cad_mcp_aec/aec_mcp_entity_tools.cpp | 2072 +++++++++-------- .../Code2026/cad_mcp_aec/aec_mcp_query.cpp | 25 +- .../Code2026/cad_mcp_aec/mcp_config.json | 163 +- 3 files changed, 1326 insertions(+), 934 deletions(-) diff --git a/SourceCode/Code2026/cad_mcp_aec/aec_mcp_entity_tools.cpp b/SourceCode/Code2026/cad_mcp_aec/aec_mcp_entity_tools.cpp index b794aed..55117dc 100644 --- a/SourceCode/Code2026/cad_mcp_aec/aec_mcp_entity_tools.cpp +++ b/SourceCode/Code2026/cad_mcp_aec/aec_mcp_entity_tools.cpp @@ -1,931 +1,1141 @@ -// ============================================================================ -// 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 -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(); - 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(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(), "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(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(), "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(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(), "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(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(), "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(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(), "beam"); - } -}; -EXPORT_MCP_TOOL(ToolUpdateBeam, UpdateAecBeamTool) +// ============================================================================ +// 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 +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(); + 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(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(), "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(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(), "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(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(), "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(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(), "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(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(), "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(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(), "climb_stair"); + } +}; +EXPORT_MCP_TOOL(ToolUpdateClimbStair, UpdateAecClimbStairTool) diff --git a/SourceCode/Code2026/cad_mcp_aec/aec_mcp_query.cpp b/SourceCode/Code2026/cad_mcp_aec/aec_mcp_query.cpp index 17cbb2b..cf0d7fb 100644 --- a/SourceCode/Code2026/cad_mcp_aec/aec_mcp_query.cpp +++ b/SourceCode/Code2026/cad_mcp_aec/aec_mcp_query.cpp @@ -17,6 +17,7 @@ #include "AecDbSlab.h" #include "AecDbColumn.h" #include "AecDbBeam.h" +#include "AecDbClimbStair.h" #include "AecDbObjXDataMap.h" using namespace aec_mcp; @@ -29,6 +30,7 @@ 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) @@ -41,6 +43,7 @@ std::string supportedTypeOf(AcDbEntity* pEnt) 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(); } @@ -141,6 +144,26 @@ nlohmann::json paramsOf(AcDbEntity* pEnt, const std::string& sType) 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); @@ -203,7 +226,7 @@ public: return nlohmann::json::parse(R"({ "type": "object", "properties": { - "type": { "type": "string", "enum": [ "all", "pipe", "wall", "slab", "column", "beam" ], + "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" }, diff --git a/SourceCode/Code2026/cad_mcp_aec/mcp_config.json b/SourceCode/Code2026/cad_mcp_aec/mcp_config.json index 3c5b8e7..7702864 100644 --- a/SourceCode/Code2026/cad_mcp_aec/mcp_config.json +++ b/SourceCode/Code2026/cad_mcp_aec/mcp_config.json @@ -1123,7 +1123,7 @@ }, { "name": "query_aec_entities", - "description": "列出本插件支持的 Envi3D 自定义实体(管道/墙/板/柱/梁),分页返回 total/returned/offset/items,每项含 handle、类型、图层、起终点、包围盒。改图之前先用它拿 handle。", + "description": "列出本插件支持的 Envi3D 自定义实体(管道/墙/板/柱/梁/爬梯),分页返回 total/returned/offset/items,每项含 handle、类型、图层、起终点、包围盒。改图之前先用它拿 handle。", "inputSchema": { "type": "object", "properties": { @@ -1135,7 +1135,8 @@ "wall", "slab", "column", - "beam" + "beam", + "climb_stair" ], "description": "要列出的类型,缺省 all" }, @@ -1734,6 +1735,164 @@ "dll_name": "cad_mcp_aec.dll", "factory_function": "UpdateAecBeamTool" } + }, + { + "name": "add_aec_climb_stair", + "description": "新建爬梯(AecDbClimbStair,对应菜单命令 直爬梯/圆护爬梯)。给梯段底部点与顶部点,以及梯宽、护笼直径、总高;其余按对话框缺省。尺寸用图纸单位。", + "inputSchema": { + "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" + }, + "space": { + "type": "string", + "description": "空间:Model 或布局名,缺省 Model" + } + }, + "required": [ + "start", + "end" + ] + }, + "backend": { + "type": "c++_dll", + "dll_name": "cad_mcp_aec.dll", + "factory_function": "AddAecClimbStairTool" + } + }, + { + "name": "update_aec_climb_stair", + "description": "修改已有爬梯:只传要改的字段(端点、梯宽、护笼直径、总高、踏步、材质…)。", + "inputSchema": { + "type": "object", + "properties": { + "handle": { + "type": "string" + }, + "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" + }, + "description": "新朝向(可选)" + }, + "material": { + "type": "string" + } + }, + "required": [ + "handle" + ] + }, + "backend": { + "type": "c++_dll", + "dll_name": "cad_mcp_aec.dll", + "factory_function": "UpdateAecClimbStairTool" + } } ] }