From ed01f00dc6e1c1d94ea7d9c883be1632a37dd099 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 | 2282 ++++++++--------- cad-mcp-aec.md | 98 + 2 files changed, 1239 insertions(+), 1141 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 55117dc..e47c8cb 100644 --- a/SourceCode/Code2026/cad_mcp_aec/aec_mcp_entity_tools.cpp +++ b/SourceCode/Code2026/cad_mcp_aec/aec_mcp_entity_tools.cpp @@ -1,1141 +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 "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) +// ============================================================================ +// 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/cad-mcp-aec.md b/cad-mcp-aec.md index 42ae746..de121f7 100644 --- a/cad-mcp-aec.md +++ b/cad-mcp-aec.md @@ -167,3 +167,101 @@ MCP 工具是通过 `PostMessage` 在主线程的消息里跑的,**不在命 `cannot open target space for write`(`eLockViolation`)—— 补了 `dwg_annotate::DocLock`; 2. 上游 `cadsdk.h` 带 `NOMINMAX`,本仓库 `KTCad.h` 没有, `std::min/std::max` 被 windows 宏顶掉 —— 在用到它们的三个文件里 `#undef min/max`。 + +## 8. 菜单命令 → 实体 → 工具 对照表 + +来源:`c:\ShangHuaSoft\Envi3D-2026-M\sys\*.mnu`(8 个文件、674 条命令、GBK 编码)。 +做法:解析 `ID_xxx [四字说明]命令` → 用命令名在源码里找注册的 handler +(`ADD_CMD` / `acedRegCmds->addCommand`)→ 在 handler(含一层直接调用)里找 `new AecDbXxx` +→ 得到"会新建实体的命令"共 74 条、涉及 44 个实体类。 + +已实现的实体:管道/墙/板/柱/梁/爬梯(每类一个 `add_*` + 一个 `update_*`)。 +其余实体的创建参数还没梳理(多数要读对话框),标"(未实现)", +要补的话照 `add_aec_climb_stair` 的写法:命令实现里抄建模序列 → 抽出参数 → 加工具。 + +|cad命令|4字中文名称|实体名称|工具名称1|工具名称2| +| --- | --- | --- | --- | --- | +|PL_CRJZ|插入角柱|AecDbColumn|add_aec_column|update_aec_column| +|PL_GZZZ|构造柱子|AecDbColumn|add_aec_column|update_aec_column| +|PL_DXBQ|单线变墙|AecDbXWall|add_aec_wall|update_aec_wall| +|PL_HZMQ|绘制幕墙|AecDbGlassCurtainWall|(未实现)|| +|PL_QTDJ|墙体倒角|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_QTDY|墙体倒圆|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_ZXPT|直段爬梯|AecDbClimbStair|add_aec_climb_stair|update_aec_climb_stair| +|PL_YHZT|圆护爬梯|AecDbClimbStair|add_aec_climb_stair|update_aec_climb_stair| +|PL_PTLT|直线钢梯|AecDbLineStair|(内部构件,不单独建)|| +|PL_SPLT|双跑楼梯|AecDbZLineStair|(未实现)|| +|PL_SFPX|双平行梯|AecDbParaLlelStair|(未实现)|| +|PL_SFZJ|双分转角|AecDbCornerStair|(未实现)|| +|PL_SFSP|双分三跑|AecDbDoubleMulStair|(未实现)|| +|PL_JCLT|交叉楼梯|AecDbScissorsStair|(未实现)|| +|PL_JDLT|剪刀楼梯|AecDbCrossStair|(未实现)|| +|PL_SJLT|三角楼梯|AecDbTriangleStair|(未实现)|| +|PL_JXZJ|矩形转角|AecDbRectCornerStair|(未实现)|| +|PL_DDLT|多段楼梯|AecDbMRPLineStair|(未实现)|| +|PL_YHLT|圆弧楼梯|AecDbArcStair|(未实现)|| +|PL_ZXLT|直线楼梯|AecDbLineSegStair|(未实现)|| +|PL_RYPD|任意屋顶|AecDbXRoof|(未实现)|| +|PL_RZPD|人字屋顶|AecDbXRoof|(未实现)|| +|PL_ZJPD|攒尖屋顶|AecDbXRoof|(未实现)|| +|PL_JXPD|<-矩形屋顶|AecDbXRoof|(未实现)|| +|PL_ZHMC|组合门窗|AecDbGroupXOpening|(未实现)|| +|PL_ZHMC|组合门窗|AecDbXOpening|(未实现)|| +|PL_DXCH|带形窗户|AecDbZLineXOpening|(未实现)|| +|PL_ZJCH|转角窗户|AecDbZLineXOpening|(未实现)|| +|PL_JLHC|加老虎窗|AecDbXDormerWindow|(未实现)|| +|PL_JBWC|加保温层|AecDbInsulation|(未实现)|| +|PL_CBKD|墙体开洞|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_CDKD|平板开洞|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_YXKD|异形开洞|AecDbX3dPart|(内部构件,不单独建)|| +|PL_YXKD|异形开洞|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_YXKD|异形开洞|AecDbXEquipment|(未实现)|| +|PL_ZLKD|阵列开洞|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_YXCG|穿管开洞|AecDbX3dPart|(内部构件,不单独建)|| +|PL_YXCG|穿管开洞|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_YXCG|穿管开洞|AecDbXEquipment|(未实现)|| +|PL_FZYT|放置阳台|AecDbBalcony|(未实现)|| +|PL_FZTJ|放置台阶|AecDbXStep|(未实现)|| +|PL_FZPD|放置坡道|AecDbAscent|(未实现)|| +|PL_FZSS|放置散水|AecDbXApron|(未实现)|| +|PL_FZHN|放置护栏|AecDbGuardBar|(内部构件,不单独建)|| +|PL_QXHN|<-曲线护栏|AecDbGuardBar|(内部构件,不单独建)|| +|PL_JFZJ|添加方井|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_JYZJ|添加圆井|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_JFYJ|加方圆井|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_JYXJ|加异形井|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_JJSK|加集水坑|AecDbX3dPartEntity|(内部构件,不单独建)|| +|PL_LSJM|绘制沟渠|AecDbEwPipeBase|(未实现)|| +|PL_YXGZ|<-异形构筑|AecDbMultiPartEntity|(未实现)|| +|PL_FZZJ|放置支架|AecDbHolder|(未实现)|| +|PL_LGGJ|立管固架|AecDbHolder|(未实现)|| +|PL_FZDJ|放置吊架|AecDbHanger|(未实现)|| +|PL_FZZD|<-放置支墩|AecDbBossHolder|(未实现)|| +|EL_HZXG|绘制线管|AecDb3dRoadAxisLine|(未实现)|| +|EL_HZXG|绘制线管|AecDbEwFlexiblePipe|(未实现)|| +|EL_JYZW|<-加乙字弯|AecDbEcFitting|(未实现)|| +|EL_HBLW|绘避雷网|AecDbEwEquipment|(未实现)|| +|EL_HBLW|绘避雷网|AecDbEwFlexiblePipe|(未实现)|| +|EL_HBLD|绘避雷带|AecDbEwEquipment|(未实现)|| +|EL_HBLD|绘避雷带|AecDbEwFlexiblePipe|(未实现)|| +|EL_JSSJ|加伸缩缝|AecDb3dRoadAxisLine|(未实现)|| +|EL_FWBR|<-翻弯避让|AecDb3dRoadAxisLine|(未实现)|| +|PL_DGTJ|单管托架|AecDbPipeHanger|(未实现)|| +|PL_SGTJ|双管托架|AecDbPipeHanger|(未实现)|| +|PL_LGTJ|立管托架|AecDbPipeHanger|(未实现)|| +|PL_PGZZ|平管支座|AecDbPipeHanger|(未实现)|| +|PL_WGZZ|弯管支座|AecDbPipeHanger|(未实现)|| +|PL_SBLG|设备连管|AecDbEwPipe|add_aec_pipe|update_aec_pipe| +|PL_RYGZ|任意管嘴|AecDbEwEquipment|(未实现)|| +|PL_XHBZ|序号标注|AecDbEwPipeDim|(未实现)|| +|PL_XHPB|序号批标|AecDbEwPipeDim|(未实现)|| +|DI_JDBZ|角度标注|AecDbDimension|(未实现)|| +|DI_JDBZ|角度标注|AecDbX3dPart|(内部构件,不单独建)|| +|DI_JDBZ|角度标注|AecDbXEquipment|(未实现)|| +|PL_DKBZ|洞口标注|AecDbEwPipeDim|(未实现)|| +|PL_TGBZ|套管标注|AecDbEwPipeDim|(未实现)|| +|PL_YHBZ|翼环标注|AecDbEwPipeDim|(未实现)|| +|PL_FMBZ|阀门标注|AecDbEwPipeDim|(未实现)|| +|HA_FBBZ|风管标注|AecDbEwPipeDim|(未实现)|| +|IS_BZFM|标注阀门|AecDbEwPipeDim|(未实现)|| +|IS_GZGJ|标注管件|AecDbEwPipeDim|(未实现)||