Commit Graph
230 Commits
Author SHA1 Message Date
company-desktop 4f1ea72e4e refactor: cad_mcp_aec 工具改按仓库既有格式描述,并合入 mcp_config.json
现象:上一版自己发明的描述文件 cad-mcp-aec.json 与仓库既有工具(add_line /
query_entities / get_entity_detail)的风格不一致——工具名前缀不同、
点用 {x,y,z} 对象而不是 [x,y] 数组、颜色叫 color_index 而不是 color。
另外 cad_mcp_frame 的运行期配置文件名是 mcp_config.json(找不到才回落
cad-mcp-frame.json),单个 cad-mcp-aec.json 还得手工合并。

改动:
1. 工具改名,与既有工具同一套动词_名词风格:
   list_aec_entities        -> query_aec_entities
   get_aec_entity           -> get_aec_entity_detail
   create_aec_pipe|wall|slab|column|beam -> add_aec_*
2. 参数约定对齐既有工具:
   - 点改用 [x,y] / [x,y,z] 数组(对象写法仍兼容,后端不变);
   - 颜色用 color(ACI,256=ByLayer),并兼容旧写法 color_index;
   - add_* 支持 space(Model 或布局名),缺省模型空间;
   - get_aec_entity_detail 的输出也从 color_index 改成 color,和入参对称。
3. 删掉自定义的 cad-mcp-aec.json,把 12 个工具按标准 MCP 字段 + backend
   (type/dll_name/factory_function)写进 cad_mcp_aec/mcp_config.json,
   与既有 28 个工具同一份文件;inputSchema 写全,带每个参数的 description。
4. 插件里的 private_name()/private_input_schema() 同步成同一套名字与 schema,
   保证配置里没写 schema 时回落也一致。

验证:
- R230 Debug / Z2026 Debug 均编译通过并部署。
- AutoCAD 2020 实测(部署目录放这份 mcp_config.json,cad_mcp_frame 优先读它):
  tools/list = 30(既有 18 + 本插件 12),12 个 add_aec_*/update_aec_* 工厂全部加载成功;
  query 列出 20 根管道;add_aec_pipe 用 [0,0,0]->[2000,0,0] + color=1 建成功,
  detail 读回 dn_mm=100/od_mm=114/wall_thickness_mm=4/color=1;
  只给 end 的 update 后端点变 2500、壁厚与颜色不变;
  add_aec_wall/slab/column/beam 各建一次并读回参数一致。

已知(与本次改动无关):mcp_config.json 里有 10 个工具在仓库当前的
cad_mcp_plugins.dll 里没有对应工厂函数(AddText/AddMText/AddLine/AddPolyline/
AddLeaderAnnotation/DeleteEntities/SetEntityProperties/DrawRevisionCloud/
FindCollisions/SetDrawingArea),会被静默丢掉,补齐需要先把那些工具的实现
补进 cad_mcp_plugins。
2026-10-29 22:26:34 +08:00
gjm c85b1b645e Merge pull request 'docs: 增加 cad_mcp_aec 的说明与实测结果' (#113) from company-desktop/company-desktop-computer/feat-cad-mcp-aec into main
Reviewed-on: #113
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-29 22:17:39 +08:00
gjm d7177a2e70 Merge pull request 'diag: 实测 3D 缓存存盘方案,判定对管道不做' (#112) from company-desktop/company-desktop-computer/diag-3d-cache-dwg-verdict into main
Reviewed-on: #112
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-29 22:16:49 +08:00
company-desktop 6f1f1062f8 docs: 增加 cad_mcp_aec 的说明与实测结果
新增 cad-mcp-aec.md:工程结构、两处关键 CMake 设置(链接 CoreCommand、
不要定义 EARTHWORM_EXPORT)、12 个工具与单位约定、三个实现坑
(文档锁 / GetSpecialtyData 必须 kForRead / SetSection 清派生字段与
SetRegionSlab 不自动闭合)、端到端实测用例表、部署与启用步骤(含把
cad-mcp-aec.json 合进运行期 cad-mcp-frame.json 的脚本)、已知限制。
2026-10-29 21:52:08 +08:00
company-desktop 132615af42 feat: 新增 cad_mcp_aec 插件,让 agent 直接建/改 Envi3D 自定义实体
现象:cad_mcp_frame 的插件机制只能挂"纯 AcDb 层"的只读工具
(cad_mcp_plugins 不链接 CoreCommand,拿不到 AecDbEwPipe / AecDbXWall /
AecDbXSlab / AecDbColumn / AecDbBeam 这些类的实现),agent 没法改仓库的自定义实体。

改动:新建 SourceCode/Code2026/cad_mcp_aec,仍是普通 DLL(由 frame 运行时
LoadLibrary),但链接 CoreCommand_BAL_REMOTE(内含静态库 CoreLibrary),
因此可以 new / 修改这五类实体。cmake 照 PipelineCmd 的写法,并在顶层
cmakelists.txt 里挂在 cad_mcp_frame 同一处(C++11+ 才编,R180/R190 跳过)。

12 个工具(每个实体一个 create + 一个 update,外加 list/get):
  list_aec_entities / get_aec_entity
  create_aec_pipe   / update_aec_pipe
  create_aec_wall   / update_aec_wall
  create_aec_slab   / update_aec_slab
  create_aec_column / update_aec_column
  create_aec_beam   / update_aec_beam

单位约定:坐标与建筑尺寸用图纸单位;管道的 dn_mm / od_mm /
wall_thickness_mm 用毫米,内部按 AS_MUNIT 转米(与 BZGDDlg、AecPipeLineTool 一致)。
update_* 只改传入字段;管道只给一个端点时另一端沿用实体现有值。

实现上撞到三个坑,都已修:
1. MCP 工具在主线程消息里跑、不在命令上下文,写库必须先
   acDocManager->lockDocument(),否则 appendAcDbEntity 返回
   eLockViolation(0x140)。用 DocLock RAII 包起来。
2. 只读实体上取专业数据必须 GetSpecialtyData(AcDb::kForRead):
   该函数缺省参数是 kForWrite,第一次实现没注意,直接把 acad 崩了
   (%TEMP%\acminidump.dmp)。
3. SetSection() 会清掉 PIPE_THICK 等派生字段("只改 DN"会把壁厚清成 0);
   AecDbXSlab::SetRegionSlab(AcGePoint2dArray&) 内部连线不补闭合点,
   传点串必须自己首尾闭合。两处都按实测结果处理了。

验证方式:
- R230 Debug、Z2026 Debug 两个 preset 编译通过并自动部署
  (cad_mcp_aec.dll,12 个工厂函数 + g_mcpPluginApiVersion 都在导出表里)。
- AutoCAD 2020 实测(临时把工具合进部署目录的 cad-mcp-frame.json,测完还原):
  tools/list 返回 30 个工具;pcf_1.dwg 上 list 出 20 根管道;
  五种实体各建一次并用 get 读回参数一致;管道改 DN+单端点后壁厚保留;
  板改厚/标高生效;类型不匹配与非法 handle 返回 -32004 / -32002。
  详见 cad-mcp-aec.md 第 5 节的用例表。

已知限制:只支持这五类实体;新建实体要等一次 REGEN 才有 bbox;
工具没有事务包装;运行期工具描述需要把 cad-mcp-aec.json 的 tools 追加到
部署目录的 cad-mcp-frame.json(插件侧的 private_description 已内置同样的文案)。
2026-10-29 21:52:05 +08:00
company-desktop ece4788723 diag: 实测 3D 缓存存盘方案,判定对管道不做
现象:工作计划第 2 项要求把自定义实体的 3D 缓存(m_ar3DDrableBufPtrs_)
用 acdbModelerSave/RestoreEntities*Filer 存进 DWG,再用 AecDbEwPipe 做开图时间对比,
判断这套机制有没有必要。

结论:按原方案对管道不做。数据(big1.dwg,AutoCAD 2020 / R230 Debug,开图 89 s):

- EwPipeBase::Draw3d 整函数 0.20~3.34 s(106~650 次),占开图 0.2~3.7%,
  而且随"开图时视图里有多少管道"变化;1088 个管道里大部分根本没画 3D。
- X3dPart::Rebuild 16.4~19.4 s(1248 次),占 18~22%,才是几何重建的大头。
- 基础体构造(GDU DrawPipe/Loft/Cylinder/Cone)合计 36.7 s,与上面有重叠,
  大头不在管道身上。

机制层面撞到三个坑(都写进文档了):

1. 附加块只能挂在叶子类。第一版挂在 AecDbEwPipeBase::dwgOut/InFields,
   AecDbBDPipeBase / XWall / XSlab / Beam / Weir 等派生类都继承它并在其后继续写字段,
   基类读尾部块等于吃掉派生类第一个字段,XWall 曲线长度读成垃圾后无限 append,
   开图从 89 s 变 10 分钟开不出来(xperf 采样定位到 AecComplexCurve::append)。
2. 增量存盘下 dwgOutFields 不会被调用。跟踪确认:开图期 AecDbEwPipe::dwgInFields
   被调 1088 次,而存盘时 AecDbEwPipe::dwgOutFields 被调 0 次;同一次存盘里
   XSlab 374 / XWall 333 / Weir 144 / Beam 126 等都写了(它们载入时被改脏)。
   ISAVEPERCENT=0、SAVEAS 到新文件都改变不了。→ "存盘顺带写字"只对本次改过的对象成立。
3. 3D 缓存本身只在画过之后才有内容,没画到的对象缓冲区是空的。

改动范围:
- 只保留两处 Debug-only 计时(AEC_PERF_COUNTER=1 才生效,Release 零开销):
  AecDbEwPipeBase::Draw3d(总耗时/重建/命中)与 AecDbX3dPart::EnsureCreated
  (构件重建耗时,作为"几何存盘"收益天花板)。
- modeler filer 试点代码(写块/读块/补非 brep)全部回退,不留死代码。
- 新增 3d-cache-dwg-verdict.md 记录数据、三个坑与建议。

验证方式:R230 Debug 编译通过并部署;big1.dwg 冷启动多次实测,
计数器见 %TEMP%\aec_section_perf.txt;卡死那次的栈用 tools/perf_diagsession.py 解析。
未做 R190 / Z2026 编译(本轮没有平台相关代码,试点代码已回退)。

已知风险:无(新增代码只在 Debug+AEC_PERF_COUNTER=1 下计时)。
2026-10-29 21:26:53 +08:00
gjm 6bae2ff493 Merge pull request 'docs: 评估两个 Autodesk 官方 skill 对本仓库的适用性' (#111) from company-desktop/company-desktop-computer/docs-acad-skills-evaluation into main
Reviewed-on: #111
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-29 19:46:04 +08:00
company-desktop 83cb1f49c8 docs: 评估两个 Autodesk 官方 skill 对本仓库的适用性
现象:工作计划第 1 项要求判断 ADN-DevTech 的 acad-api-skill 与
acad-memory-leak-skill 对本仓库是否有帮助。两份 skill 都没有在仓库里留下结论,
下次还得重新读一遍。

结论(写入 acad-skills-evaluation.md):
- acad-api-skill:全篇 .NET 10 托管插件 / AutoCAD 2027 / NuGet / .csproj,
  与本仓库 C++ ObjectARX + CMake + ZRX 双平台零重叠,模板还撞红线 2
  (禁止新增 .vcxproj/.sln)。唯一有价值的是 skills/code-sleuth.md 里提到的
  Autodesk Knowledge MCP 端点。
- acad-memory-leak-skill:方法论与本仓库现状对得上——本仓库 Debug 也链
  Release CRT(HRL-MODEL-refactor.md:222 已记),_CrtSetDbgFlag 这类 CRT
  自检不可用,内存只能从进程外部测,正是该 skill 的全部内容。但 4 个脚本里
  3 个有缺陷。

文档里记录的实测证据(本轮真跑过,不是抄文档):
- Autodesk Knowledge MCP 免注册可用,直接返回 C++ 签名,例如
  acdbModelerRestoreEntitiesFromDwgFiler(AcDbDwgFiler*, AcArray<AcDbEntity*>&,
  int* pModelerVersion, unsigned restoreOptions)(dbacis.h),可为工作计划第 2 项
  查 modelerVersion/restoreOptions 语义;locale 必须用 en_US 下划线形式。
- 本机 WPT 齐全且当前会话是 admin;skill 自带的 VirtualAlloc .wprp 实测
  wpr -start/-stop 干净出 ETL。据此修正 open-drawing-perf-summary.md 里
  "wpr -stop 本机报 0x80010106" 的旧结论:现在可正常使用。
- run_with_metering.ps1 无条件把 "迭代数 模式" 拼进 argv,给 acad.exe 用会
  起不来(实跑复现);dump_regions.ps1 不创建 -OutDir 却在结尾 Resolve-Path,
  首次调用必失败;run_with_wpa.ps1 的 .wprp 声明了 HeapEventProvider 却没挂进
  Collectors,堆栈图拿不到 heap 数据;plot_leak.py 依赖未安装的 matplotlib。

改动范围:仅新增一篇文档,不动任何源码、脚本与工程文件。
验证方式:文档内容对应本轮的实测记录;未改代码,无需编译与 CAD 实测。
已知风险:无。建议采纳项(挂 MCP、改两个测量脚本、抽成 skill)经确认为
暂不实施,仅留档。
2026-10-29 19:45:13 +08:00
gjm f549eb552c Merge pull request '工作计划' (#110) from company-desktop/工作计划 into main
Reviewed-on: #110
2026-10-29 16:00:41 +08:00
gjm 17e77b448d Merge pull request 'perf: 给基础体构造加计时,定位到 DrawPipe/DrawCone/DrawLoft 共占 33%' (#109) from company-desktop/company-desktop-computer/perf-primitive-counters into main
Reviewed-on: #109
2026-10-29 16:00:21 +08:00
gjm 01b2099437 Merge pull request 'docs: 增加 cad_mcp_frame 的移植说明与代码审查' (#108) from company-desktop/company-desktop-computer/feat-cad-mcp-frame into main
Reviewed-on: #108
2026-10-29 16:00:02 +08:00
gjm c526009a7d Merge pull request 'docs: 汇总打开大图纸的性能结论(截面对画之外)' (#107) from company-desktop/company-desktop-computer/doc-open-perf-summary into main
Reviewed-on: #107
2026-10-29 15:59:17 +08:00
company-desktop 0ea937868e 工作计划
工作计划
2026-10-29 15:56:46 +08:00
company-desktop c25f3edb23 fix: 修 MCP 插件的版本校验与加载期弹窗,18 个工具全部可用
端到端实测(/b 脚本按 AecRxBase -> CoreCommand -> cad_mcp_frame 顺序加载)
暴露两个真问题:

1) 版本校验拿到垃圾值,15/16 个工具被误踢
   原实现 GetProcAddress(hMod,"acrxGetApiVersion") 转函数指针调用,
   和基座自己的 acrxGetApiVersion() 比数值。实测两边返回值是乱的
   (1507328 / 2052390912 / 0 交替,其中 1507328=0x170000 才是正确的 23.0),
   结果只有第 3 个工具加载成功,其余全被判 SDK 版本不匹配。
   改为插件导出数据符号 g_mcpPluginApiVersion=(ARX<<16)|SUB_ARX
   (mcp_plugin_api.h 新增 EXPORT_MCP_PLUGIN_VERSION(),每个插件 DLL 调用一次),
   基座 GetProcAddress 读内存比对,不涉及调用约定;旧插件没有该符号时只告警按兼容处理。

2) 加载期 AfxMessageBox 会卡住主线程
   mcp_ConfigParser 里的几处弹窗全部换成 McpLog:
   同时写 %TEMP%\mcp_plugin_load.log 和命令行,并补上每个工具的
   [try]/[skip] 明细(dll 是否存在、版本、create/destroy 指针)。
   这也是"看起来只加载 3 个工具"的原因——弹窗把主线程挡住了。

实测结果(R230,自动化烟测):
  端口 127.0.0.1:8080          6 s 内监听
  tools/list                   返回 18 个工具(2 lisp_inline + 16 c++_dll)
  5 KB 大请求                  -32601,证明整包被读完(旧实现会截断)
  tools/call get_layers        真正执行插件,返回 {"count":1,"items":[{"name":"0",...}]}
  加载日志                     16 个 c++_dll 全部 [try] 成功,0 个 [skip]

另外把配置文件加入构建部署:copyarx.exe 能拷任意文件,照 set_debug_post_build 的写法
再加一条 POST_BUILD,把 cad-mcp-frame.json 也拷到部署目录(ZWCAD 走 zw_output_path.json)。
不部署的话 frame 起来后 LoadAndRegister 找不到配置会直接自关。

验证:R230 与 Z2026 的 frame + plugins 均编译通过并部署;R230 已做上述端到端实测。
2026-10-29 06:33:19 +08:00
company-desktop ecb8436720 feat: 移植 cad_mcp_plugins,并对齐中望平台的符号与依赖
插件工程在 CadProject/src/cad_mcp_plugins(8 个 cpp/h),本次一并整合进 code2026。

移植改动:
- 源码拷到 SourceCode/Code2026/cad_mcp_plugins/;cadsdk.h -> KTCad.h
- 输出是普通 DLL(.dll),由 cad_mcp_frame 运行时 LoadLibrary,不注册 ARX 命令
- 新增 cad_mcp_plugins/cmakelists.txt:按 code2026 惯例
  (target_set_name_and_suffix ".dll" / target_link_objectarx / 预编译头 / SUB_ARX)
- mcp_plugin_api.h 从 cad_mcp_frame/inc 上移到 SourceCode/Code2026/inc:
  frame 与 plugins 共用,且在 ShsGlobalIncludes 的 include 路径里两边都可见;
  frame 的 CMake 去掉已删除的本地 inc 目录
- 顶层在同一个 C++11+ 条件里挂载 add_custom_module_2(cad_mcp_plugins CadMcpPlugins)
- 中望适配:源码里的 #pragma comment(lib,"rxapi.lib") 在中望下没有这个库(叫 ZwRx.lib),
  该 pragma 与导出名都按 #ifdef ZWCAD 分开(中望导出 zcrxGetApiVersion);
  frame 里 GetProcAddress 查插件版本号的函数名同样按平台分

验证:
- R230:cad_mcp_frame.arx 826 KB + cad_mcp_plugins.dll 866 KB,已自动部署
- Z2026:cad_mcp_frame.zrx 825 KB + cad_mcp_plugins.dll 864 KB,已自动部署
- 配置里 16 个 c++_dll 工具的工厂函数(CreateXxxTool)全部出现在 dll 导出表里
- 所有 .cpp/.h 为 UTF-8 with BOM

未做:没有在 CAD 里实跑。另外配置文件本身没有随构建部署(set_debug_post_build 只拷产物),
需要把 cad-mcp-frame.json(或改名 mcp_config.json)与 LISP 目录放到部署目录,
否则 frame 起来后 LoadAndRegister 失败会直接自关。详见 cad_mcp_frame-review.md。
2026-10-29 00:16:37 +08:00
company-desktop 47d7b96cad perf: 给基础体构造加计时,定位到 DrawPipe/DrawCone/DrawLoft 共占 33%
继续追"剩余效率问题"。上一轮已确认 Parasolid 的 93.4% 发生在我们的视口绘制里,
本轮机位下一层:给 GiDrawUtility 的基础体构造函数加计时。

新增 inc/PerfCounterLite.h:header-only 的轻量计数器。
AecRxBase.arx 不链接 CoreLibrary(静态库只被 CoreCommand 链接),拿不到 SectionPerf
的符号,所以单独做一份;只在 Debug + AEC_PERF_COUNTER=1 时生效,Release 零开销。
统计写 %TEMP%\aec_prim_perf.txt(独立文件,避免提前写出 aec_section_perf.txt
让自动化脚本误判"开图完成")。

接入 GiDrawUtility:DrawPipe 三个重载、DrawLoft 两个重载、DrawCone、DrawCylinder。

实测(big1.dwg,93 s 开图 / 81 s CPU):
  DrawPipe(region)      7201 次  13.2 s  平均 1837us  最长 130ms
  DrawLoft(reg)          883 次  5.27 s  平均 5969us
  DrawLoft(curve)        883 次  5.90 s  平均 6679us
  DrawCone               811 次  4.64 s  平均 5717us
  DrawPipe(pts)/Cylinder 2411 次  4.07 s  平均 1688us
去重后约 27 s,占 81 s CPU 的 33%。

结论(详见 perf-primitive-cost.md):
- 7201 次管道构建 vs 约 2260 个管类实体 = 3.2 次/实体,护栏/爬梯/设备细节也在造管道
- 单次成本差一个量级:圆柱 1.7ms 最便宜(已是优化过的快路径),圆锥 5.7ms、放样 6.0~6.7ms 最贵
- 长尾明显:DrawPipe 最长 130ms(平均的 70 倍),存在个别大件
- 都在"首次构建"路径上,没有重复重建,所以只能少建 / 建得更便宜 / 跨实体复用同参数几何

验证:R230 Debug 编译通过,tools/run_acad_perf.ps1 自动开图取数;
截面缓存与回写仍为正常/0(未受本次插桩影响)。
2026-10-28 23:32:22 +08:00
company-desktop b9c4ce56f9 fix: cad_mcp_frame 修 P0 收包/socket 泄漏与 P1 静默失败、无鉴权
P0 收包协议(大请求被截断 + 分包即失败)
原来只 recv 一次、缓冲固定 2048 字节。改为:
- IsJsonComplete():按 {} 与 [] 配平判断报文完整性(跳过字符串内与转义字符)
- RecvOneRequest():select + 循环 recv 追加,单次等 5s、整请求 30s、上限 4MB
- SendAll():send 循环到发完(tools/list 应答可能超一个包)
不改客户端协议:客户端仍是 sendall 后等响应断开。

P0 socket 泄漏
recv<=0 的分支原来什么都不做。现在收包失败/超时回一个 JSON-RPC 错误帧并 closesocket;
accept 失败直接 continue(原代码对 INVALID_SOCKET 也调 closesocket)。

P1 监听失败不再静默
socket/WSASocket/bind/listen/CreateWindowEx/std::thread 全部检查返回值,
监听 socket 改在主线程创建,失败时 init_mcp_server() 返回 false 并打印 WSAGetLastError,
端口被占用时能看到 "Fatal: bind 127.0.0.1:8080 failed, err=10048"。

P1 端口与配置文件名可配
- server.port 可配(默认 8080)
- 配置先找 mcp_config.json,再找 cad-mcp-frame.json(此前代码只找前者,
  而仓库里带的是后者,导致加载即自关)
- 启动日志打印实际端口与 token 是否启用

P1 可选 token 鉴权
配置写了 server.token 就要求请求根级带 token,不匹配返回 -32001 且不执行工具;
没配 token 保持原行为,不破坏现有客户端。

顺带 P2:close_mcp_server() 先排空挂起的 WM_MCP_EXECUTE_TASK 并 delete TaskData
(原来会漏 socket + 内存),再销毁窗口。

验证:R230 与 Z2026 Debug 均编译通过(818->826 KB,已部署)。
未做:没有在 CAD 里实际跑 tools/list 与 tools/call 验证协议改动。
2026-10-28 23:28:02 +08:00
company-desktop 1dec459be9 docs: 增加 cad_mcp_frame 的移植说明与代码审查
移植已完成(R230 ARX + Z2026 ZRX 均编译通过并部署),本文记录:
- 移植做了哪些改动(cadsdk.h->KTCad.h、两个单头依赖、SUB_ARX、中望 .def 与版本号函数)
- C++11+ 限制:R180/R190 是 C++98,无法编译 nlohmann/json,故按 preset 条件跳过
- 架构:后台线程收包 -> PostMessage -> 主线程执行,工具调用全在主线程,避开了跨线程访问数据库
- 问题清单(按严重度):
  P0 收包只 recv 一次、缓冲固定 2048:大报文截断、TCP 分包即失败,且没有长度前缀
  P0 bytesReceived<=0 时 socket 泄漏
  P1 socket/bind/listen 返回值全未检查,端口占用时静默失败但 g_bRunning 仍为 true
  P1 无鉴权:本机任意进程都能调用会改图的工具
  P1 端口 8080 硬编码;代码找 mcp_config.json 而仓库里是 cad-mcp-frame.json
  P2 卸载时挂起的 PostMessage 任务不释放;LISP 工具是异步 fire-and-forget 无结果回传;
     加载期弹 AfxMessageBox 会卡住 CAD 启动;g_hMsgWnd 无同步;
     .def 的 LIBRARY 名与实际输出不一致;/Zc:forScope 语义与 code2026 全局设置相反
- 移植后仍需处理:统一配置文件名、移植 cad_mcp_plugins、带上 lisp 目录、在 CAD 里实测
2026-10-28 23:20:14 +08:00
company-desktop 0d02d33f1d feat: 把 cad_mcp_frame 整合进 Code2026 的 CMake 体系
源工程在 d:\new-start\CadProject\src\cad_mcp_frame(vcpkg 独立工程),
本次按 code2026 的 preset/CMake 体系移植到 SourceCode/Code2026/cad_mcp_frame。

移植改动:
- 源码 + .def + .rc + res/ + cad-mcp-frame.json 拷入 cad_mcp_frame/;
  插件接口头 mcp_plugin_api.h 放到 cad_mcp_frame/inc/
- cadsdk.h -> KTCad.h(两者除尾部 4 个 include 与 using namespace MemOper 外完全一致)
- nlohmann/json 单头放 SourceCode/dep_inc/nlohmann/json.hpp,
  ghc::filesystem 单头放 SourceCode/dep_inc/ghc/filesystem.hpp;
  target_link_objectarx 已把 DEP_INC_ROOT 加进 include 路径,故无需额外配置
- 新增 cad_mcp_frame/cmakelists.txt:按 PipeHangerCmd 的写法
  (add_def_file_name / target_set_name_and_suffix .arx / target_link_objectarx /
  target_include_directories inc / set_debug_post_build)
- 补 SUB_ARX 宏:原工程由 CadProject 的 cmake 注入,code2026 只注入 ARX;
  在模块自己的 CMake 里补,不动共享 functions.cmake(中望给 0)
- 顶层挂载 add_custom_module_2(cad_mcp_frame CadMcpFrame "SH/Other"),
  并加条件:nlohmann/json 与 ghc::filesystem 都需要 C++11+,
  而 R180/R190 是 C++98,故只在 (IS_ZWCAD OR CAD_SDK_VERSION >= 20) 时编译
- 中望适配两处:_zrx.def 要导出 zcrxEntryPoint/zcrxGetApiVersion;
  代码里显式声明的 acrxGetApiVersion() 不会被 SDK 宏改名,
  改为 #ifdef ZWCAD 取 zcrxGetApiVersion()

验证:
- R230 Debug 编译通过,cad_mcp_frame.arx 818 KB,自动部署到
  c:\ShangHuaSoft\Envi3D-2026-M\R230\
- Z2026 Debug 编译通过,cad_mcp_frame.zrx 818 KB,自动部署到 ...\Z2026\
- R190/R180 按条件跳过(C++98 无法编译 nlohmann/json)
- 所有 .cpp/.h 均为 UTF-8 with BOM;cmakelists.txt 保持 ASCII 无 BOM

未做:没有在 CAD 里加载实测;配置文件仍是 cad-mcp-frame.json,
而代码里找的是 mcp_config.json,加载前需要统一(详见 cad_mcp_frame-review.md);
插件工程 cad_mcp_plugins 未移植(18 个工具里 16 个依赖它)。
2026-10-28 23:20:07 +08:00
company-desktop 97826360fb docs: 汇总打开大图纸的性能结论(截面对画之外)
把这条线的结论收成一个文档,避免散落在多个 PR 的分析里。

内容:
- 优化前后数字:开图墙钟 113 s -> 93 s,截面恢复 22.4 s -> 9.4 s,
  开图期回写 1140 -> 0,临时文件 6773 -> 1733
- 剩下时间的归属(优化后 81,046 采样):
  base_ViewportDraw 之下 74.6%,Parasolid 67.5%,
  且 Parasolid 的 93.4% 发生在我们的视口绘制里;我们的 arx 自耗时 <1%
- 结论:剩余成本是"构件数量 x 单构件建模成本",
  既不是 CAD 自己在拖时间(绘制之外只剩 6.6%),也不是我们的代码慢(<1%)。
  一次开图要建 1335 个 AcDbX3dSolid + 1248 个 AecDbX3dPart,计数器验证每个只建一次。
- 还能动的方向按性价比排序:减少要建的几何(LOD)、几何跨会话持久化(Phase 3)、
  用基本体替代扫掠、截面再优化(已判定不做,收益 <0.5%)
- 诊断方法与踩过的坑:采样算不出次数必须用计数器;加密构建下函数级符号化不可靠
  (GuardBar::Draw3d 报 27% 实为误判);VSDiagnostics 本机会卡死、wpr -stop 有 COM bug,
  改用 xperf on/stop/merge;RVA 必须用 PDB 解析再下结论;计数器别写进同一份报告文件

验证:纯文档,数据来自 tools/run_acad_perf.ps1 + tools/perf_diagsession.py 的实测。
2026-10-28 23:05:13 +08:00
gjm dd9b49e5a4 Merge pull request 'fix: AecDbX3dPart 记住"已尝试建过",避免 Hide 构件被反复重建' (#106) from company-desktop/company-desktop-computer/fix-x3dpart-retry into main
Reviewed-on: #106
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 22:56:18 +08:00
company-desktop facf14699b fix: AecDbX3dPart 记住"已尝试建过",避免 Hide 构件被反复重建
现象:开图时 X3dPart 的 EnsureCreated 命中率只有 10.1%,1914 次重建里
721 次产出 NULL(38%),单个对象最多被重建 544 次。

根因:EnsureCreated 的命中条件是 !m_bNeedCreate && m_pSolid != NULL,
而 Rebuild() 里对 Hide 构件是"故意不建几何、直接 return true"——
m_pSolid 按设计就是 NULL,于是每次访问都重新走一遍 Rebuild,永远不可能命中。
子项全为空的构件同理。

修复:
- 新增 m_bCreated 标记"已经尝试建过"(无论是否产出实体)
- EnsureCreated 命中条件改为 !m_bNeedCreate && (m_bCreated || m_pSolid != NULL)
- Rebuild 入口置 m_bCreated = true;所有设 m_bNeedCreate = true 的 setter
  同时置 m_bCreated = false,保证参数变化后仍会重建
- 重建后仍无实体时打一条日志(限前 5 条,带 m_sName),方便定位是哪些构件

实测(big1.dwg / R230 Debug):
  X3dPart 命中率      10.1% -> 41.4%
  重建次数            1914  -> 1248(-666)
  rebuild_null         721  -> 55
  单对象最多重建       544  -> <=3
  开图墙钟             93 s -> 93 s(无变化)

注意:这 666 次空转没有带来可测的提速,因为 Rebuild 里 Hide 的早退分支在
布尔运算之前,每次只有几微秒。所以这是正确性/整洁度修复,不是性能修复;
但消除了"个别构件被重试几百次"这种病态行为,也暴露了"哪些构件建不出实体"。

顺带新增诊断(Debug + AEC_PERF_COUNTER=1 才生效,Release 无开销):
- SectionPerf::NoteEnsure():按 tag 统计"延迟建几何"的命中/重建,
  并给出对象维度直方图与重建次数最高的对象
- AecDbX3dPart::Rebuild / AcDbX3dSolid::EnsureCreated / AecDbGuardBar::Draw3d 打点
- tools/run_acad_perf.ps1 增加 -CpuTrace:用 xperf 采 CPU 采样并 merge 成 etl,
  可直接喂 tools/perf_diagsession.py(VS 的 VSDiagnostics 在这台机器上会卡住,
  wpr -stop 有 COM 线程模式 bug,故用 xperf)

验证:R230 Debug 编译通过并部署,自动开图取数;截面缓存与回写仍为 0/正常。
2026-10-28 22:51:01 +08:00
gjm dffee29702 Merge pull request 'diag: 拆分"写临时文件"与"acisIn"计时,判定内存读取不值得做' (#105) from company-desktop/company-desktop-computer/diag-inmemory-verdict into main
Reviewed-on: #105
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 22:11:34 +08:00
company-desktop 56c89c5fa4 diag: 拆分"写临时文件"与"acisIn"计时,判定内存读取不值得做
修完回写后开图期仍有 1733 次临时文件写入,需要判断"改内存读取"能省多少。

在 RegionAcisInDwgInFields 与 CoreTools::StringToRegion 两个重载里,把
"写临时文件+关闭"和"AcDbBody::acisIn"拆成独立计时(RegIn.write / RegIn.acisIn /
Str2Reg.write / Str2Reg.acisIn)。

实测(big1.dwg,93 s 开图,1733 次未命中):
  RegIn.write    374 次    70.5 ms   189 us
  RegIn.acisIn   374 次  1422.0 ms  3.80 ms
  Str2Reg.write 1359 次   267.7 ms   197 us
  Str2Reg.acisIn 1359 次 5999.5 ms  4.41 ms

结论:写文件合计 338 ms,只占 9.6 s 恢复耗时的 3.5%;杀软扫描已在 write 的 190 us 里
(Defender 在关闭时扫)。即使完全省掉文件往返,上限也只有 0.3~0.5 s(开图的 0.4%)。
而 AcDbBody::acisIn 在 R19~R26 只有文件名重载,真要内存解析只能改存盘格式走
acdbModelerRestoreEntityFromDwgFiler(即暂缓的 Phase 3)。因此不做。

剩余 7.4 s 是纯解析(1733 次 × 4.3 ms),只能靠减少次数;text 通道 1560 个唯一文本
是真实不同的截面,命中率已接近上限。继续提速应转向 Parasolid 绘制那条线。

数据与结论已写入 pipe-section-region-performance-plan.md。
本次只加诊断计时(Debug + AEC_PERF_COUNTER=1 才生效),不改任何业务行为。
2026-10-28 22:09:48 +08:00
gjm 3c18a715a4 Merge pull request 'perf: 护栏/爬梯等构件的管道副本改为"只存参数",消除开图期 1140 次截面回写' (#104) from company-desktop/company-desktop-computer/perf-defer-pipe-clone into main
Reviewed-on: #104
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 22:01:39 +08:00
company-desktop b981fe930a perf: 护栏/爬梯等构件的管道副本改为"只存参数",消除开图期 1140 次截面回写
现象:开图期间 AecDbEwPipeBase::RegionAcisoutDwgOutFields 被调用 1140 次、耗时 2.38 s,
把截面写成临时 SAT 文件。用 AEC_WRITEBACK_TRACE 抓栈 + tools/resolve_rva.ps1 解符号,
定位到 AecDbZLineStair::DrawGuarBar3d 里的 pipe.clone():
clone 走 AcDbObject → AecDbEwPipeBase::copyFrom → CopyObject(整实体 dwgOutFields/dwgInFields
往返),于是截面被写成临时文件再读回。

这些 clone 的唯一用途是存进 m_pipes,供 ToJson() 导出 __pipes__,而截面参数在
SetSection 时已经写进 spec XData(KEY_SECSHAPE / KEY_SIZE1 / KEY_SIZE2 / PIPE_THICK /
KEY_SECCEN),完全可以按参数重建。因此把"建几何"推迟到导出时:

- AecDbEwPipeBase::BuildSectionFromXData():按 XData 参数重建截面(圆形/矩形;
  自定义截面返回 NULL,那种只能存在于 SAT 里)
- AecDbEwPipeBase::CloneParamsOnly():生成只带参数(XData、放样线、实体属性、GUID、
  Breaks)、不含截面几何的副本,不触发任何序列化
- AecDbEwPipeBase::ToJson():GetSection() 为空时回退到 BuildSectionFromXData(),
  保证 __section__ / __section2__ 仍然导出
- 30 处 m_pipes.push_back((AecDbEwPipe*)pipe.clone()) 改为 pipe.CloneParamsOnly():
  ClimbStair(10)、ZLineStair(6)、BossHolder(4)、MRPLineStair(4)、Hanger(3)、Holder(3)

过程:先只改楼梯两个文件,回写从 1140 降到 960;再抓栈解析出剩余 960 全部来自
AecDbClimbStair::DrawLadderH3d,于是把其余 20 处一并改掉,最终归零。

实测(big1.dwg / AutoCAD 2020 / R230 Debug):
  开图墙钟        初始 108~113 s  -> 缓存后 98 s  -> 本次 93 s
  开图期回写      1140 次/2.38 s  -> 1140 次       -> 0
  RegionAcisIn    3082 次         -> 3082 次       -> 1942 次
  临时文件        6773/9.34MB     -> 2694/3.34MB   -> 1733/2.34MB
  截面恢复总耗时  22.4 s          -> 9.87 s        -> 9.4 s

另外把 SectionPerf::TraceCallStack 的输出改写到独立的 aec_writeback_trace.txt,
不再写进 ENVIPERFDUMP 的报告文件(否则脚本会把"栈写出来了"误判成"开图完成")。

验证:R230 Debug 编译通过并部署;用 tools/run_acad_perf.ps1 自动开图取数。
待验证:JSON 导出。m_pipes 里现在是不含几何的参数副本,ToJson 靠
BuildSectionFromXData() 重建,需要实际触发一次导出,确认 __pipes__ 里每条仍有
__section__ / __section2__ 且与改动前一致。
2026-10-28 21:57:31 +08:00
gjm 439505d65a Merge pull request 'diag: 统计回写的实体类,定位到 AecDbEwPipeBase 基类实例' (#103) from company-desktop/company-desktop-computer/diag-writeback-copyfrom into main
Reviewed-on: #103
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 21:30:49 +08:00
company-desktop 7038a98441 diag: 用 PDB 解析栈帧,定位到 AecDbEwPipeBase::copyFrom 里的多余序列化
上一版只给了"模块+RVA"的栈,我据此先后推断成
AecDbCurveBase::subGetTransformedCopy 和 AecDbEntityCommon::base_DeepClone,两者都是错的
(用户在那里下断点不命中;新加的 PerfTraceMark 打点也没有触发)。本次用符号解析定死。

本次改动:
- 新增 inc/PerfTraceMark.h:inline 的"某函数被调用过"打点,Debug 且 AEC_WRITEBACK_TRACE=1
  才生效。AecRxBase.arx 不链接 CoreLibrary(静态库只被 CoreCommand 链接),拿不到 SectionPerf,
  所以做成 header-only。
- 在 5 个候选入口打点:AecDbCurveBase::subGetTransformedCopy / subDeepClone、
  AecDbEntityBase::subDeepClone、AecDbEntityCommon::base_DeepClone、CKTCadUtility::CloneObject。
  跑一次开图,5 个都没触发 —— 这些候选被排除。
- 新增 tools/resolve_rva.ps1:用 dbghelp 的 SymLoadModuleEx / SymFromAddr 配本地 PDB,
  把"模块+RVA"解析成函数名。Windows 没有自带 symbolizer,这个脚本很轻,可反复用。

解析结果(CoreCommand.arx):
  0x58f051  AecDbEwPipeBase::copyFrom+0x41
  0x7e1300  AecDbZLineStair::DrawGuarBar3d+0x2c0
  0x7e09d0  AecDbZLineStair::DrawSteep3d+0xbe0
  0x7ddcef  AecDbZLineStair::Draw3d+0x34f

真实成因:Z 型楼梯画护栏(DrawGuarBar3d)时 copyFrom 一份管道实体,
而 AecDbEwPipeBase::copyFrom 用通用的 CopyObject(inc/AecDbEntityCommon.h:331)
做"整实体 dwgOutFields/dwgInFields 往返"来拷字段,于是截面被写成临时 SAT 文件再读回来。

而且这次序列化是多余的:copyFrom 随后把所有字段又用内存级方式拷了一遍
(曲线 clone/copyFrom、截面 clone/copyFrom、XData CopyFrom、m_arUserInfs append、
标志位/图层/线型/Breaks 逐项赋值),WriteUserInterface 写的也只是 m_arUserInfs。
所以最小修改是把那句 CopyObject 去掉,并补上源为 NULL 时清空目标的分支
(目前这个"清空"是 CopyObject 顺带完成的)。

本次只做定位与记录(分析写进 pipe-section-region-performance-plan.md 并纠正了上一版的误判),
不改 copyFrom 的行为——它影响所有管系实体的复制粘贴/块内拷贝,需要单独验证。
2026-10-28 21:28:00 +08:00
gjm 973ae2c69f Merge pull request 'diag: 统计回写的实体类,定位到 AecDbEwPipeBase 基类实例' (#102) from company-desktop/company-desktop-computer/diag-writeback-class into main
Reviewed-on: #102
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 21:25:46 +08:00
company-desktop 6d6f9ddaff diag: 统计回写的实体类,定位到 AecDbEwPipeBase 基类实例
上一版只抓到"绘制期深拷贝"的调用栈,还没回答"是哪个类的实体在回写"。
现在 SectionPerf 增加按类统计的写回计数(NoteWriteBackClass),
在 RegionAcisoutDwgOutFields 里记一次 isA()->name()。

实测(big1.dwg,开图一次):

  ---- write-backs by entity class ----
    1140     AecDbEwPipeBase

全部 1140 次都是 AecDbEwPipeBase 基类实例,不是 AecDbEwPipe 也不是具名子类,
与属性 key 统计里 AecDbEwPipeBase 的 1172 次几乎一一对应——
说明图纸里约有 1172 个基类实例(由 AecDbAssembleWell.cpp 的装配井代码创建,
多为矩形截面段),绘制期每个被深拷贝一次。

完整链路(已写入计划文档):
  base_ViewportDraw -> AecDbCurveBase::subGetTransformedCopy
  (AecRxBase/AecDbCurveBase.cpp:307 里的 clone())
  -> AecDbEntityCommon::base_DeepClone(AecDbEntityCommon.cpp:1232,dwgOut 在 1265)
  -> AecDbEwPipeBase::dwgOutFields(AecDbEwPipeBase.cpp:1440)
  -> AecDbEwPipeBase::RegionAcisoutDwgOutFields(AecDbEwPipeBase.cpp:1275)

顺带发现:AecDbEwPipeBase::copyFrom(:1605)复制截面用的是 m_pRegion->copyFrom(),
是内存级 ACIS 拷贝,比"写文件再读回"便宜得多。所以最局部的修法可能是给管系实体
加 subGetTransformedCopy 重载,用 isA()->create()+copyFrom() 代替 clone(),
绕开整条序列化链路;但 clone 与 copyFrom 语义不完全等价,需先核对。
本次只记录分析,不改行为。

验证:R230 Debug 编译通过并部署,脚本自动开图一次得到上述分类统计。
2026-10-28 21:06:22 +08:00
gjm 85e210d036 Merge pull request 'diag: 增加调用栈抓取,定位开图期 1140 次截面回写的成因' (#101) from company-desktop/company-desktop-computer/diag-writeback-trace into main
Reviewed-on: #101
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 21:00:36 +08:00
company-desktop 67d6545e6c diag: 增加调用栈抓取,定位开图期 1140 次截面回写的成因
现象:开图期间 EwPipe::RegionAcisout 被调用 1140 次、耗时 2.38 s,
把几何又序列化回 SAT。之前只知现象不知来源。

新增 SectionPerf::TraceCallStack():用 RtlCaptureStackBackTrace + GetModuleHandleExW
把栈帧解析成 "模块名 + RVA" 并写进报告文件。正式代码里不开启,
只在 AEC_WRITEBACK_TRACE=1 时于 RegionAcisoutDwgOutFields 首次调用处抓一次。

结论(栈已定位):
  subViewportDraw -> base_ViewportDraw -> ... -> base_DeepClone
  -> AcDbObject::dwgOut -> AecDbEwPipeBase::dwgOutFields -> RegionAcisout

即:不是保存,而是绘制期的深拷贝。AecDbEntityCommon::base_DeepClone
(AecRxBase/AecDbEntityCommon.cpp:1265)用
  AcDbDeepCloneFiler filer; GetMe()->dwgOut(&filer); pClone->dwgIn(&filer);
实现拷贝,于是每次克隆实体,截面都要写一个临时 SAT 文件再读回来。
读的那一半现在已被 kText 缓存吃掉,剩下 acisOut 写文件这半(2.38 s)。

顺带修正一个文档缺陷:inc/SectionPerfCounter.h 里 %TEMP%\aec_section_perf.txt
的注释之前被转义吃掉变成了 %TEMP%<BEL>ec_section_perf.txt。

修法(跳过深拷贝期的区域序列化 / 直接写缓存里的 SAT 原文)都需要动
AecDbEntityCommon 的拷贝契约或先审计 m_pRegion 的写操作,风险面不一致,
只把分析与三种方案写进 pipe-section-region-performance-plan.md,本次不改行为。

验证:R230 Debug 编译通过并部署;AEC_WRITEBACK_TRACE=1 跑图一次,栈如文档所示。
2026-10-28 20:49:42 +08:00
gjm 709786db6f Merge pull request 'perf: 增加截面 Region 缓存,开图恢复耗时降 56%' (#100) from company-desktop/company-desktop-computer/perf-section-region-cache into main
Reviewed-on: #100
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 20:45:58 +08:00
company-desktop f8d10d5a3b perf: 增加截面 Region 缓存,开图恢复耗时降 56%
一次开图要做 5641 次截面恢复(22.4 s),其中大量内容重复,
而每次恢复都是"写临时 SAT 文件 + 从盘 acisIn + 删文件",单次 2.7~5.5 ms,
还要过一次杀软。本提交加一级母体缓存,命中直接 clone 返回。

新增 CoreLibrary/SectionRegionCache.{h,cpp},三个通道按 key 的正确性强度分开:
- kParam:纯参数 hash,用于 AecDbEwFitting::GetSection 的两个重载
  (key = dD/dt/HLR 标志 或 W/H/dt/HLR 标志,同参数必同几何)
- kAttr:类名 + spec XData(排除编号,并跳过 Id/点/向量/矩阵/二进制类型),
  只给 AecDbEwPipe 用。AecDbEwPipeBase 是很多类的基类,其余派生类实例间的
  区别就是截面,不满足"同数据必同截面",所以一律走内容 hash
- kText:SAT 文本内容 hash,用于 RegionAcisInDwgInFields 的其它类
  与 CoreTools::StringToRegion 的两个重载

约定:母体由缓存持有、永不外泄,Acquire/Put 都返回 clone,调用方拥有返回值;
母体不入库;每条通道 4096 条上限(AEC_SECTION_CACHE_MAX 可调),超出按 FIFO 淘汰;
AEC_SECTION_CACHE=0 可整体关闭做 A/B;卸载时 ClearAll() 释放,避免 DLL 析构顺序问题。

关键发现:SAT 文本第 2 行带存盘时间戳
  16 Autodesk AutoCAD 19 ASM 219.0.0.5600 NT 24 Thu Oct 22 10:19:20 2026
同一几何存盘时间不同,整份文本就不同(采样里两份 2596 字节的文本只差
10:19:20 与 10:19:16),这正是内容 hash 命中率只有 54% 的原因,与截面无关。
因此新增 HashSatText():算 hash 时跳过第 2 行,仅当该行含 "Autodesk" 时才跳过。
计数器也改用同一口径,避免两套统计互相矛盾。

实测(big1.dwg / AutoCAD 2020 / R230 Debug,AEC_SECTION_CACHE 关 vs 开):
  开图墙钟        108~113 s      -> 98 s
  截面恢复总耗时  22.4 s         -> 9.87 s(-56%)
    StringToRegion  14.05 s      -> 7.73 s
    RegionAcisIn     8.38 s      -> 2.14 s
  临时文件        6773 次/9.34MB -> 2875 次/3.61MB
  命中率          text 65.5% / attr 84.1% / param 88.8%
  clone 开销      命中 58us、未命中 73us(合计 0.37 s,可忽略)

顺带:
- tools/run_acad_perf.ps1 增加 -CacheOff 开关,用于同一构建上的 A/B 对比
- SectionPerfCounter 增加 AppendReport(),让缓存统计也写进同一份报告文件
- 诊断口子 AEC_SAT_SAMPLE=n 可导出未命中的 SAT 原文,用于定位这类差异

验证:R230 Debug 编译通过并部署;用脚本自动开图各跑一次(缓存关/开),
数据已写入 pipe-section-region-performance-plan.md。
尚未做:R190 编译(本机缺 v100 工具集)、Release 构建的收益复测、
开图期间 1140 次 RegionAcisout 回写(与本缓存无关,需单独立项)。
2026-10-28 20:41:05 +08:00
gjm 28dd149f2f Merge pull request 'docs: 记录属性 key 命中率实测结果' (#99) from company-desktop/company-desktop-computer/perf-attr-key-hitrate into main
Reviewed-on: #99
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 20:14:39 +08:00
company-desktop fd5bbb60ab docs: 记录属性 key 命中率实测结果
同日第二次跑图(113 s)的数据:属性 key 合计 86.7% 命中,远高于内容 hash 的 54.0%,
其中 AecDbEwPipe 84.1%(173 种唯一)、AecDbEwPipeBase 99.7%(仅 3 种唯一),
证实"同数据的管子/管件截面必然相同"这个业务前提。

同时记录覆盖率限制:属性 key 只能覆盖 EwPipe::RegionAcisIn 这条路径(3082/5641 次),
另外 2559 次走 CoreTools::StringToRegion(设备、保温等),无 spec 数据,只能用内容 hash。
据此把 Phase 1 定为混合策略,并给出预计收益:省 14.9 s / 22.6 s 恢复开销,
约占开图的 13%。

留一个明确的未验证点:属性 key 命中时两份 SAT 文本是否真的相同;
若存在同数据不同文本(旧版本存盘、不同代码路径),直接复用有风险,
需要 textHash 兜底校验,该项在 Phase 1 实现时顺带统计。
2026-10-28 20:06:11 +08:00
company-desktop 764e544db4 perf: 增加属性 key 命中率统计(Phase 0 补充)
意图:内容 hash 只看到 54% 命中率,但业务上"同数据的管子/管件截面必然相同",
所以要用实体属性做 key 再量一次,决定 Phase 1 到底用哪种缓存键。

新增 SectionPerf::AttrKey(RAII):
- 构造时按类名(isA()->name())初始化 hash 与统计桶,析构时记一次
- Add() 增量喂字节,调用方决定哪些字段参与 key
- 统计按实体类分开(total / unique / hits / hit_rate)

接入 AecDbEwPipeBase::RegionAcisInDwgInFields:key = 类名 + 全部 spec XData,
但排除编号("No.",每根管子唯一)并跳过 Id / 点 / 向量 / 矩阵 / 二进制类型
(与截面无关或随实体变化)。只在 AEC_PERF_COUNTER=1 时构建,Release 无开销。

实测(big1.dwg,AutoCAD 2020 / R230 Debug,113 s 开图):

  属性 key 合计 3082 次 / 2672 命中 = 86.7%
    AecDbEwPipe       1088 次  173 唯一  84.1%
    AecDbEwPipeBase   1172 次    3 唯一  99.7%
       (其余类 43%~99%)
  内容 hash 54.0%

结论:属性 key 显著优于内容 hash,但只覆盖 3082/5641 次恢复
(另 2559 次走 CoreTools::StringToRegion,拿不到 spec 数据,只能用内容 hash)。
混合策略预计省 14.9 s / 22.6 s 恢复开销,约占 113 s 开图的 13%。

数据已写入 pipe-section-region-performance-plan.md,并标注一个未验证点:
属性 key 命中时两份 SAT 文本是否真的相同(决定 Phase 1 是否需要 textHash 兜底校验)。

验证:R230 Debug 编译通过并部署,用 tools/run_acad_perf.ps1 自动跑图取数。
2026-10-28 20:06:06 +08:00
gjm 217d7f5f77 Merge pull request 'docs: 记录 Phase 0 实测结果、AutoCAD 自动化实测环境与属性 key 的取舍' (#98) from company-desktop/company-desktop-computer/fix-section-perf-ndebug into main
Reviewed-on: #98
2026-10-28 19:54:14 +08:00
company-desktop 6559cdf6f5 docs: 记录 Phase 0 实测结果、AutoCAD 自动化实测环境与属性 key 的取舍
Phase 0 在本机跑通(big1.dwg / AutoCAD 2020 / R230 Debug,开图 108 s):

  RegionTexts=5641  UniqueHashes=2597  SimulatedHits=3044  HitRate=54.0%
  TempFiles=6773  TempBytes=9,341,125  WriteBacks=1140
  CoreTools::StringToRegion  2559 次  14.6 s  平均 5.7 ms  最长 321 ms
  EwPipe::RegionAcisIn       3082 次   8.3 s  平均 2.7 ms
  EwPipe::RegionAcisout      1140 次   2.3 s  平均 2.0 ms

结论:一次开图做 5641 次 SAT 恢复,共 22.9 s(约占 108 s 的 21%)。
此前基于 CPU 采样的估算只有 550~1000 次,低估了 5~10 倍——说明采样只能算占比,
算不出调用次数,这类结论必须靠计数器。
按 54% 命中率折算,Phase 1 缓存净省约 12 s(约 11%),并少约 4000 次临时文件写盘。

同时记录三个待追查项:命中率为何只有 54%、StringToRegion 为何比 RegionAcisIn 贵一倍、
开图期间为何回写 1140 次 SAT。

文档里补充了"用管子属性做 key"的思路与风险:属性相同则截面相同,业务上成立,
但不能单独当 key——DWG 里的 SAT 文本可能来自旧版本/旧路径/手工编辑的异形截面,
属性漏一项就会静默复用错误几何。若要做,采用 L1 内容 hash + L2 属性 key 的两级结构,
L2 命中后仍比对 textHash,不一致按 miss 处理;落地前先加一路属性 key 命中率计数比较。

另外新增 tools/run_acad_perf.ps1 并在 AGENTS.md 记录本机 CAD 实测环境:
acad.exe 路径、/p SHS-ENVI2026 启动参数、测试图纸、部署目录,
以及"PowerShell 5.1 把无 BOM 的 UTF-8 当 ANSI 读"这个坑(脚本保持 ASCII)。

验证:文档与脚本;脚本语法检查通过,本次实测就是用它的等价命令跑出来的。
2026-10-28 19:52:18 +08:00
company-desktop 3f387485fd fix: 计数器改用 NDEBUG 判断构建类型(本仓库把 _DEBUG 剥掉了)
现象:Debug 构建的 R230 里 ENVIPERFDUMP 报"未知命令",计数器也永远不生效。

根因:cmakelists.txt 第 23~24 行会把 /D_DEBUG 从 CMAKE_CXX_FLAGS_DEBUG 里正则剥掉,
所以即使 --config Debug 编译,_DEBUG 也没有定义。于是 Phase 0 里用
`#if defined(_DEBUG)` 包住的命令注册和 Enabled() 判定整块被编掉——
已用二进制验证:修复前 arx 里查不到 ENVIPERFDUMP,修复后能查到(UTF-16 字符串)。

修复:改用 NDEBUG。cmakelists.txt 只对 Release 定义 NDEBUG
(`$<$<CONFIG:Release>:NDEBUG>`),所以 `#ifndef NDEBUG` 正好等价于"Debug 构建"。

- CoreLibrary/SectionPerfCounter.cpp:计数器启用条件
- CoreCommand/EarthWorm.cpp:ENVIPERFDUMP / ENVIPERFRESET 的函数定义与命令注册

验证:R230 Debug 两个 target 重新编译通过,产物已部署;
脚本自动开图后 ENVIPERFDUMP 正常输出(数据已记录到计划文档 Phase 0 实测结果)。
已知风险:`#ifdef _DEBUG` 在本仓库本来就不生效,rxdebug.cpp 等历史代码同样如此,
本次只修自己新增的两处,没有动别处。
2026-10-28 19:52:18 +08:00
gjm 6c090b5636 Merge pull request 'perf: 增加截面恢复路径的 Phase 0 计数器(Debug 探针)' (#97) from company-desktop/company-desktop-computer/perf-section-counter into main
Reviewed-on: #97
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 19:47:09 +08:00
company-desktop 3afc2dd7c1 perf: 增加截面恢复路径的 Phase 0 计数器(Debug 探针)
按 pipe-section-region-performance-plan.md 的 Phase 0 落地。目的不是提速,而是先拿到
三个决定性数字,再判断截面缓存(Phase 1)到底值不值得做:
1) 每次截面恢复多贵(µs),一次开图共多少次;
2) 同一份 SAT 文本被重复恢复多少次(唯一 hash 数 / 模拟命中率)——直接决定缓存收益上限;
3) 临时文件写入次数与字节数(杀软扫描影响面),以及打开图纸时的回写次数。

改动:
- 新增 inc/SectionPerfCounter.h + CoreLibrary/SectionPerfCounter.cpp
  - Scope(QPC 计时)+ NoteRegionText(FNV-1a 64 位 hash,统计唯一数与模拟命中)
    + NoteTempFile + NoteWriteBack + Dump/Reset
  - 计数器实例放在函数内 static:CoreLibrary 是静态库且只被 CoreCommand 链接,
    所以 CoreCommand.arx 里只有一份,两个入口共用
- AecDbEwPipeBase.cpp:RegionAcisInDwgInFields / RegionAcisoutDwgOutFields 接入
- CoreTools.cpp:StringToRegion 两个重载 / RegionToString 接入
- EarthWorm.cpp:注册 ENVIPERFDUMP / ENVIPERFRESET(仅 Debug)
- CoreLibrary/cmakelists.txt 加一个源文件
- 计划文档的 Phase 0 补上实际命令与采集项

开关:启动 CAD 前 set AEC_PERF_COUNTER=1;命令 ENVIPERFDUMP 打印并追加到
%TEMP%\aec_section_perf.txt,ENVIPERFRESET 清零。
Release 下 Enabled() 恒为 false,调用点只剩一次 bool 判断。

不改任何几何逻辑与存盘格式。

验证:R230 Debug 编译通过(CoreLibrary_BAL_REMOTE、CoreCommand_BAL_REMOTE,无新增 warning),
产物已自动部署到 c:\ShangHuaSoft\Envi3D-2026-M\R230\。
R190 未在本机编译(缺 v100 工具集);代码刻意只用 C++98 特性,控制台输出改走 %hs,
避开了 &wstring[0] 这类老 STL 上的写法问题。
尚未做 CAD 实测:需要开图后跑 ENVIPERFDUMP 才看得到数据。
2026-10-28 19:31:27 +08:00
gjm 4f2833fd91 Merge pull request 'feat: 通知脚本支持 Telegram 通道' (#96) from company-desktop/company-desktop-computer/feat-notify-telegram into main
Reviewed-on: #96
2026-10-28 15:58:08 +08:00
company-desktop 18aa1d1260 feat: 通知脚本支持 Telegram 通道
用户没有企业微信,改用 Telegram Bot。同一台机器的 api.telegram.org 需要走本地代理
(实测 127.0.0.1:7897),所以发送逻辑要显式支持代理。

- notify.json 新增 telegram 段:token / chatId / proxy
  (也认环境变量 PI_NOTIFY_TELEGRAM_TOKEN、PI_NOTIFY_TELEGRAM_CHAT)
- 未配置 proxy 时回落 HTTPS_PROXY 环境变量,再回落系统(IE)代理
- 文本超过 3900 字符截断(Telegram 上限 4096)
- 自检 -Probe 同时报告 webhook 与 telegram 两个通道的结果,token 只显示前 10 位
- SKILL.md 更新通道配置说明,补上 notify.json 的完整示例与代理注意事项

实测:假 token 时返回 "(401) 未经授权",说明请求确实穿过了代理到达 api.telegram.org,
只是凭据无效;未配置时正确报告 not configured。真实推送待用户提供 bot token。
2026-10-28 15:55:13 +08:00
gjm 254387f187 Merge pull request 'feat: 需要用户决策时主动通知(桌面 toast / 决策日志 / IM 机器人)' (#95) from company-desktop/company-desktop-computer/feat-notify-on-decision into main
Reviewed-on: #95
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 15:52:27 +08:00
company-desktop 46d76317c7 feat: 通知脚本支持本地 webhook 配置与连通性自检
接微信/钉钉时 URL 是密钥,不能进仓库,也不适合每次都写在命令行里。

- webhook 取值顺序:-Url 参数 > 环境变量 PI_NOTIFY_WEBHOOK >
  %USERPROFILE%\.pi\agent\notify.json 的 webhook 字段 > notify_webhook.txt 纯文本
  (两个备用文件都在仓库之外,不会被提交)
- 新增 -Probe:只做一次连通性自检并打印结果(URL 打码,只显示前 32 位与后 8 位)
- 修正一个误判:企业微信/钉钉出错时仍返回 HTTP 200 + errcode,
  原来只判断 HTTP 成功会把"密钥无效"当成发送成功。
  现在解析响应体,errcode/code 非 0 一律视为失败并把原因带出来
  (实测假 key 返回 errcode=93000 invalid webhook url)

验证:-Probe 在"未配置"和"配置了假 key"两种情况下都给出正确结论,
未做真实推送(还缺用户的企业微信机器人地址)。
2026-10-28 15:51:00 +08:00
company-desktop dff08b17a3 feat: 需要用户决策时主动通知(桌面 toast / 决策日志 / IM 机器人)
背景:用户可能不在终端前面(远程连过来、或者走开了),agent 把问题打在终端里就停住,
人根本不知道,等于空等。

新增:
- tools/notify_decision.ps1
  - Windows toast(WinRT),失败则回退到模态弹窗(120 秒自动关),并带提示音
  - 追加写入 %USERPROFILE%\.pi\agent\decisions\pending.md,漏看 toast 也不丢问题
  - 可选 webhook:-Url 或环境变量 PI_NOTIFY_WEBHOOK;
    企业微信/钉钉用 {"msgtype":"text",...},URL 含 feishu/lark 时自动换飞书格式
  - 中文必须走 -MessageFile(UTF-8 读取),避免命令行参数被控制台代码页弄乱
  - 任何失败都 exit 0,通知出问题不能反过来中断 agent
- .pi/skills/notify-decision/SKILL.md:什么时候该通知、消息里必须写清"要决定什么 /
  选项与代价 / 我的建议与默认动作"三件事、通道配置方式
- AGENTS.md:加一小节指向该 skill,并在仓库内 skill 表格里登记

验证:本机跑通三种调用(ASCII -Message、中文 -MessageFile、加 -NoBeep),
中文在决策日志里显示正常;toast 通过 PowerShell appId 注册,WinRT 接口检测为可用。
仅新增工具与文档,未触碰任何编译产物与源码。
2026-10-28 15:48:23 +08:00
gjm 88ba197ae7 Merge pull request 'docs: 增加管道截面恢复性能改造计划' (#94) from company-desktop/company-desktop-computer/plan-pipe-section-region-perf into main
Reviewed-on: #94
Reviewed-by: gjm <littleoldpan@163.com>
2026-10-28 15:44:42 +08:00
company-desktop 38bf61072f docs: 调整截面改造计划:本轮只做缓存,存盘格式保持 SAT
上一版把"改用 acdbModelerSaveEntityToDwgFiler 存盘"列为 Phase 3,经讨论后本轮暂缓,
本次重写计划文档的范围与结构。

暂缓存盘改造的理由:
1. 收益不明确 —— 无法确认 modeler filer 通道比 "SAT 文本 -> 临时文件 -> acisIn" 快多少,
   目前能确认的只有"省掉临时文件、从而省掉杀软扫描时间"这一条;
2. 这条收益在 Phase 1 之后很小 —— 缓存落地后落盘次数已经降到 O(唯一截面数),通常几十次;
3. 复杂度不低 —— 不能升 kCurrentVersionNumber(AecDbEwPipeBase.cpp:938 里
   version > kCurrentVersionNumber 会直接 eMakeMeProxy,老版本整实体变 proxy)、
   读端必须延后构造否则会白读一遍 SAT、DWG 体积翻倍、还要过整套老版本兼容验证矩阵;
4. region-sat-plan.md §3.8 已确立"一条写路径"原则,双写与之相悖。

本轮范围改为:
- Phase 0 计数器(Debug + 环境变量开关),拿单次成本与文本 hash 唯一数;
- Phase 1 截面内容缓存(按 SAT 文本 hash 缓存 region 母体,命中返回 clone),
  覆盖 AecDbEwPipeBase 全部子类 + CoreTools::StringToRegion 的调用方;
- Phase 2 临时目录治理(可选,止血)。

因为本轮不涉及任何按版本/平台分叉的代码,§4 的平台矩阵简化为"全平台一致、无需能力宏";
将来若重启存盘改造再按 NOT IS_ZWCAD AND CAD_SDK_VERSION GREATER_EQUAL 20 算能力宏
(不能直接在源码里比版本号,ZRX 的 CAD_SDK_VERSION 是年份,与 ARX 19~26 数值重叠)。

调研结论没有丢,收进两个附录备查:
- 附录 A:SDK 能力边界(acisIn 只有文件名重载;modeler filer 从 R20.0 起才有;
  save 需要裸 ENTITY*,只能走标了 INTERNAL USE ONLY 的 getLockedASMBody/setASMBody);
- 附录 B:将来若做双写必须注意的点(红线不能升版本号、读端延后构造的逻辑漏洞与修法、
  块格式建议、七项老版本兼容验证矩阵、体积代价、helper 抽取方式)。

验证:仅文档,未改代码,无编译与 CAD 实测。
2026-10-28 15:44:11 +08:00
company-desktop 9167303cce docs: 增加管道截面恢复性能改造计划
背景:diagsession 实测显示,打开大图纸时"截面恢复"是仅次于 Parasolid 的第二大开销,
且每个实体都要落一次 SAT 临时文件(写盘 + acisIn + 删除),会被杀软逐个扫描。

实测依据(report.diagsession,CAD 2019 系 / R190 部署,Debug):
- AecDbEwPipeBase::RegionAcisInDwgInFields 栈内 7.30%
- CoreTools::StringToRegion 栈内 6.88%
- CoreTools::RegionToString(反向回写)3.07%
- 参照:base_ViewportDraw 74.28%,Parasolid ≈43%
说明:采样只统计在跑 CPU 的时间,临时文件 I/O 等待不进采样,实际墙钟占比高于上表。

计划分四段,每段可独立回退:
- Phase 0 先加计数器(Debug + 环境变量开关),拿到单次成本与文本 hash 唯一数,
  用复用率决定后续是否值得投入。
- Phase 1 截面内容缓存:按 SAT 文本 hash 缓存 region 母体,命中返回 clone。
  平台/版本无关,不动存盘格式;覆盖 AecDbEwPipeBase 全部子类 + StringToRegion 的调用方;
  落盘次数从 O(实体数) 降到 O(唯一截面数)。
- Phase 2 临时目录治理:改用已有的 CoreTools::getTempFilePath 带父目录重载,
  父目录从 INI 读,可加入杀软排除。
- Phase 3(R20+ 且非 ZRX)改用 dbacis.h 的 acdbModelerSave/RestoreEntityFromDwgFiler,
  几何直接写进 DWG filer,不再产生 SAT 文本、不再落盘。

版本/平台分支:产品从 ARX R20 起支持,R190 与中望保留现有 SAT 分支。
按 AGENTS.md 红线,不在源码里比较 ARX 版本号(ZRX 的 CAD_SDK_VERSION 是年份 2023~2026,
与 ARX 19~26 数值重叠),改为在 cmakelists.txt 里算好能力宏 AEC_HAS_MODELER_FILER,
源码只用该宏切换。

已核实的 SDK 边界(文档内附版本矩阵):
- AcDbBody::acisIn 全版本只有文件名重载,没有 buffer 版;Arx 里也没有 api_restore_entity_list,
  所以"完全不落盘"只能走 modeler filer 通道。
- acdbModeler*FromDwgFiler 从 R20.0 起才有,R19.0 没有。
- acdbModelerSaveEntityToDwgFiler 需要裸 ENTITY*,只能通过标了 INTERNAL USE ONLY 的
  getLockedASMBody/setASMBody 取得,因此 Phase 3 必须先做 round-trip 最小验证。

文档同时记录了存盘兼容性决策(S1 配置切换 / S2 按版本自动 / S3 双写,推荐 S1 且默认仍写 SAT)、
风险与回滚、验证清单(R230/R190/ZRX 编译 + CAD 实测 + 复录 trace 对比)和 5 个未决问题。

验证:仅文档,未改代码,无编译与 CAD 实测。
2026-10-28 15:29:24 +08:00