docs: cad-mcp-aec.md 增加菜单命令-实体-工具对照表 #119

Merged
gjm merged 2 commits from company-desktop/company-desktop-computer/feat-aec-mcp-entities-p2 AGit into main 2026-10-30 08:35:25 +08:00
4 changed files with 493 additions and 3 deletions

No files matched your search

@@ -22,6 +22,7 @@
#include "AecDbSlab.h"
#include "AecDbColumn.h"
#include "AecDbBeam.h"
#include "AecDbClimbStair.h"
#include "AecDbObjXDataMap.h"
#include "KTSetting.h"
@@ -929,3 +930,212 @@ public:
}
};
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<AecDbClimbStair>(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<std::string>(), "climb_stair");
}
};
EXPORT_MCP_TOOL(ToolUpdateClimbStair, UpdateAecClimbStairTool)
@@ -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" },
+161 -2
View File
@@ -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"
}
}
]
}
+98
View File
@@ -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|(未实现)||