diff --git a/pipe-section-region-performance-plan.md b/pipe-section-region-performance-plan.md
new file mode 100644
index 0000000..f318d6c
--- /dev/null
+++ b/pipe-section-region-performance-plan.md
@@ -0,0 +1,330 @@
+# 管道截面(Region)恢复性能改造计划
+
+> 目标:降低"打开大图纸"时**截面恢复**的开销。
+> 依据:`diagsession-cpu-analysis.md` 的实测数据;`report-cpu-hotspots.md` 为原始表格。
+> 相关既有结论:`GiDrawUtility-analysis.md` §7.4 E2、`AecDbEwPipeBase-region-sat-plan.md` §3.8。
+
+## 0. 结论摘要(本轮范围)
+
+**本轮只做运行期优化,不动存盘格式。**
+
+1. **Phase 0:先测量**(不改行为,Debug + 环境变量开关)。
+2. **Phase 1:截面内容缓存**(核心)——按 SAT 文本 hash 缓存 region 母体,命中返回 `clone()`。
+ 平台/版本无关,不涉及任何格式/协议改动,覆盖 `AecDbEwPipeBase` 全部子类 + `CoreTools::StringToRegion` 的调用方。
+ 预期:临时文件落盘次数从 **O(实体数)** 降到 **O(唯一截面数)**(通常几十次),CPU 省 **约 6~10 个百分点**。
+3. **Phase 2:临时目录治理**(可选,止血)——把 SAT 临时文件从 `%TEMP%` 挪到可配置目录,可加入杀软排除。
+
+**明确不在本轮范围**:改用 `acdbModelerSaveEntityToDwgFiler` 存盘(含"双写"方案)。
+决策理由见 §8,调研结论与将来要做时的注意事项见附录 A/B,避免以后重新踩一遍。
+
+**长期唯一硬要求**:新版本软件必须能读老图纸。当前 SAT 文本通道继续保留,天然满足。
+
+## 1. 数据依据
+
+本机第二轮 trace(`report.diagsession`,CAD 2019 系 / R190 部署,Debug 构建):
+1 ms 采样,acad.exe 42,888 个带栈采样 ≈ **42.9 s CPU**。
+
+| 指标(栈内出现率,每栈去重) | 采样 | 占 acad CPU |
+| --- | ---: | ---: |
+| `AecDbEwPipeBase::RegionAcisInDwgInFields` | 3,130 | **7.30 %** |
+| `CoreTools::StringToRegion` | 2,949 | **6.88 %** |
+| `AecDbEwPipeBase::dwgInFields`(上层) | 3,420 | 7.97 % |
+| `CoreTools::RegionToString`(反向回写) | 1,315 | 3.07 % |
+| 参照:`AecDbEntityCommon::base_ViewportDraw` | 31,856 | 74.28 % |
+| 参照:Parasolid(ASM* 家族独占) | — | ≈ 43 % |
+
+两条恢复入口有部分重叠,保守估计 **截面恢复 ≈ 10~14 %**。
+
+**两个必须记住的采样局限**:
+
+- 采样只统计**在跑 CPU** 的时间,临时文件 write/read 的**等待不进采样** →
+ 实际墙钟占比 **高于** 上表。
+- 这里的 10~14 % 是"载入期付一次";绘制期还会再走扫掠/布尔,两者是不同问题(见 §7 第 4 条)。
+
+## 2. 现状:相关代码路径
+
+### 2.1 `AecDbEwPipeBase::RegionAcisInDwgInFields`(`AecDbEwPipeBase.cpp:794`)
+
+DWG 里存的是 **SAT 文本 + 一个文件名**。恢复流程:
+
+```cpp
+pFiler->readString(&pszFileName); pFiler->readUInt32(&nLen); pFiler->readBytes(pszTxt, nLen);
+sFullPath = FilePath::getSystemTemporaryFolder() + "\\" + SanitizeRegionTempName(sFileName);
+file.Open(sFullPath, CFile::modeCreate | CFile::modeWrite); file.Write(pszTxt, nLen); file.Close();
+AcDbBody::acisIn(sFullPath, arBodyPtrs); // 从盘上解析
+::DeleteFile(sFullPath);
+```
+
+该函数是 `AecDbEwPipeBase` 的方法 → **`AecDbEwPipe` 及所有管件子类共用**
+(`AecDbEwPipeBase.cpp:910` 由 `SectionRegionDwgInFields` 调用,`:955` 赋给 `m_pRegion`)。
+
+### 2.2 `CoreTools::StringToRegion`(`CoreTools.cpp:3519` / `:3581`)
+
+同样的"写临时文件 → `acisIn` → `DeleteFile`",比 2.1 多一步 `fixACISFormat` 改写文本。
+调用方覆盖 `AecDbEwEquipment`、`AecDbInsulation`、`AecDbBDPipeBase`、`PipelineCmd`(`EQU_MODIFY.cpp`)等。
+另有 `CoreTools::StringToBody`(`:3712`)同一模式。
+
+### 2.3 绘制期:直管几何的构建(`AcDbX3dSolid.cpp:1242~1300`)
+
+```cpp
+createFromCurves([外圆 pl]) → pRegO // 第 1 次
+createFromCurves([内圆 pPlI]) → pRegI // 第 2 次
+extrudeAlongPath(pRegO, ln) → pSolidO // 扫掠 1
+extrudeAlongPath(pRegI, ln) → pSolidI // 扫掠 2
+pSolidO->booleanOper(kBoolSubtract, pSolidI) // 布尔减
+```
+
+`GiDrawUtility::DrawPipe`(`GiDrawUtility.cpp:938`)里另有 `pRegion->clone()` + `transfromRegion()`,
+因为原 region 会被变换。**这也是 Phase 1 能零风险接入的原因:clone 语义本来就存在。**
+
+### 2.4 存盘现状(本轮不改)
+
+- `kCurrentVersionNumber = 7`(`AecDbEwPipeBase.cpp:382`)
+- region 块布局:`writeUInt32(0)`(SAT 通道标志)+ `RegionAcisoutDwgOutFields` 写 SAT 文本
+ (`SectionRegionDwgOutFields:1279`)
+- `dwgInFields:938` 有 `if (version > kCurrentVersionNumber) return Acad::eMakeMeProxy;`
+
+## 3. 本轮方案
+
+### Phase 0:先测量(不改行为)
+
+在 `RegionAcisInDwgInFields` 与 `StringToRegion` 两个入口 + 临时文件写/删处加计数,
+`_DEBUG` 编译进、环境变量 `AEC_PERF_COUNTER=1` 开启、命令 `ENVIPERFDUMP` 打印汇总:
+
+| 计数项 | 用途 |
+| --- | --- |
+| 调用次数、单次/累计 µs(QPC 包住整段) | 单次成本(预估 3~15 ms) |
+| 输入文本 hash 的**唯一值数** | 复用率 = 1 − 唯一/总数 → Phase 1 收益上限 |
+| **模拟命中率**(只统计、不改行为) | 同上,更直观 |
+| 临时文件写入次数 + 累计字节 | 杀软影响面 |
+| `RegionToString` / `RegionAcisoutDwgOutFields` 次数 | 查清打开时为何在回写(3.07 %) |
+
+**决策点 D0**:唯一数是否远小于总次数(例如 30 种 × 1500 次)。
+- 是 → Phase 1 直接做
+- 否(每个实体文本都不同)→ 停下重新评估(此时缓存收益接近 0)
+
+### Phase 1:截面内容缓存(核心,全平台)
+
+新增一个缓存模块(建议 `CoreLibrary/SectionRegionCache.h/.cpp`,私有实现放匿名 namespace):
+
+- **key**:输入 SAT 文本字节的 64 位 hash(自实现 FNV-1a/xxHash,不引第三方),长度参与计算。
+ → **内容寻址,不需要任何失效逻辑**:样式/参数变了,文本变了,key 自然变。
+- **value**:**母体** `AcDbRegion*`(heap 对象,不入库,永不外泄)。
+- **API**:
+ - `AcDbRegion* SectionRegionCache::Acquire(const char* psz, Adesk::UInt32 nLen)` —— 命中返回 `clone()`,未命中返回 NULL
+ - `void SectionRegionCache::Put(const char* psz, Adesk::UInt32 nLen, AcDbRegion* pMaster)` —— 由调用方在成功构造后存入
+- **容量**:条目上限(如 512)+ 总字节上限(如 32 MB)+ LRU;条目析构 `delete` 母体。
+- **线程**:先加 `std::mutex`(trace 显示 98 % 单线程,开销可忽略)。
+ 若将来绘制并行,注意 modeler 实体不能跨 partition 共享,需改为每线程缓存(注释里写清楚)。
+
+接入点(两处即可覆盖绝大部分):
+
+1. `AecDbEwPipeBase::RegionAcisInDwgInFields` —— 在**写临时文件之前**先查缓存。
+2. `CoreTools::StringToRegion`(两个重载)与 `StringToBody` —— 同样在写文件前查缓存。
+
+**不变量(必须守住)**:
+
+- 缓存只发 **clone**;调用方拿到的永远是独立对象(现有 `DrawPipe` 的 `clone()` + `transfromRegion()` 不受影响)。
+- 母体不被任何调用方 `delete`。
+- miss 路径的 `DeleteFile` 逻辑保持原样(含失败分支)。
+
+### Phase 2:临时目录治理(可选,止血)
+
+Phase 1 之后残余落盘只剩"每个唯一截面一次",收益小;但如果现场仍被杀软拖慢,可做这一步:
+
+- 现状两处硬编码 `%TEMP%`,而 `%TEMP%` 通常**不在**客户"工作目录排除"范围内 → 每次落盘都被扫。
+- 改用仓库已有的 `CoreTools::getTempFilePath(main, suffix, pszParentPath, ...)`(`CoreTools.h:495`),
+ 父目录从 INI 读(如 `SectionTempDir`),空/不存在时回落系统临时目录。
+- 安装/部署脚本把该目录加入 Defender 排除:`Add-MpPreference -ExclusionPath "
"`。
+- 顺带收益:现在临时文件名来自 DWG 里存的 `sFileName`,多实体同名会互相覆盖
+ (`temp-file-naming.md` 已记录此类隐患);缓存命中时该隐患消失。
+
+## 4. 平台与版本矩阵
+
+本轮**不引入任何按版本/平台分叉的代码**,因此:
+
+| 编译目标 | 本轮改动 |
+| --- | --- |
+| ARX R19.0(AutoCAD 2013-2014,本地习惯目标) | Phase 0/1/2 全部生效 |
+| ARX R20.0 ~ R26.0 | 同上 |
+| ZRX Z2023 ~ Z2026 | 同上 |
+
+不需要能力宏。将来若重启"换存盘通道"(附录 B),再按下面的方式加开关,
+**不要**在源码里比较 ARX 版本号:`cmakepresets.json` 里 ZRX 的 `CAD_SDK_VERSION` 是年份
+(`2023`~`2026`),与 ARX 的 `19`~`26` 数值区间重叠,直接比较必然误判(AGENTS.md 红线)。
+
+```cmake
+# 将来需要时:在 cmakelists.txt 里与现有 ZWCAD 判定放在一起
+if (NOT IS_ZWCAD AND CAD_SDK_VERSION GREATER_EQUAL 20)
+ add_compile_definitions(AEC_HAS_MODELER_FILER)
+endif()
+```
+
+## 5. 风险与回滚
+
+| 风险 | 影响 | 应对 |
+| --- | --- | --- |
+| 缓存母体被调用方污染 | 几何错乱 | 只发 clone;母体不去库、不外泄;Code Review 专项检查 |
+| `AcDbRegion::clone()` 深拷贝语义不足 | 共享脏数据 | 沿用现有 `m_pRegion->clone()` 的语义;验证"同一母体 clone 两次分别变换,互不影响" |
+| hash 碰撞 | 用错截面 | 64 位 hash + 长度参与;可选命中后做一次字节比对(成本远低于重建) |
+| 内存增长 | 大图纸爆内存 | 条目数 + 总字节双上限 + LRU;打印峰值供现场反馈 |
+| 回写(`RegionToString` 3.07 %)原因不明 | 白做一次序列化 | Phase 0 计数确认;若确属不必要,单独一个 PR 修掉 |
+
+**回滚**:Phase 1 由宏/配置一键关闭(走原路径);Phase 2 只改路径来源;
+Phase 0 只在 Debug 编译进,无发布风险。
+
+## 6. 验证清单
+
+编译:
+
+- [ ] `R230`(日常调试目标)Debug/Release 编译通过
+- [ ] `R190` 编译通过
+- [ ] 至少一个 ZRX preset(如 `Z2026`)编译通过
+- [ ] 改动涉及 `.cpp/.h` 时为 UTF-8 with BOM;`cmakelists.txt` 保持 ASCII 无 BOM
+
+功能(CAD 实测,逐项记录):
+
+- [ ] 新建管道 → 存盘 → 重开:截面几何、面积、位置一致
+- [ ] 老图纸(曲线通道 / SAT 文本通道)打开后截面正确
+- [ ] 老图纸另存 → 重开,截面仍正确
+- [ ] 复制粘贴(`copyFrom` 走 `m_pRegion->clone()`)不丢截面
+- [ ] 管件、设备(`AecDbEwEquipment`)、保温(`AecDbInsulation`)截面均正确
+- [ ] 同一母体 clone 出的两个 region 分别做不同变换,互不影响
+
+性能(用 `tools/perf_diagsession.py`):
+
+- [ ] 同一张图、同一 preset、同一构建配置下各录一次 trace 对比
+- [ ] 关键指标:截面恢复占比、临时文件写入次数、`base_ViewportDraw` 总占比、`AecDbX3dPart::EnsureCreated` 命中情况
+- [ ] 记录墙钟耗时(点开到可操作)与 CPU 采样时间两个口径
+
+## 7. 未决问题
+
+1. **D0**:SAT 文本的唯一 hash 数到底是多少?(Phase 0 用计数器回答)
+2. 打开图纸时为什么在**回写** SAT(`RegionToString` 3.07 %)?是 `copyFrom`/深拷贝走了 `dwgOutFields`,
+ 还是别的原因——Phase 0 计数确认后再定是否单独立项。
+3. Phase 1 之后 `AecDbX3dPart::EnsureCreated/Rebuild` 仍每次都重建(栈上 8.4 % 却不命中)——
+ 这是**另一个独立问题**,优先级可能高于截面缓存(见 `diagsession-cpu-analysis.md` §4)。
+4. `AcDbBody::acisIn` 只有文件名版本;若将来想彻底去掉临时文件,只能走附录 B 的通道
+ (或自行接入 ACIS API,成本与授权需另评)。
+
+## 8. 决策记录:为什么本轮不改存盘格式
+
+考虑过的方案是"**双写**":既写现有 SAT 文本,又把几何用
+`acdbModelerSaveEntityToDwgFiler` 写成一个附加块放到存盘数据末尾,新版本优先读新块,
+老版本忽略尾部数据 → 兼顾"新版本更快"与"老版本可读"。
+
+**暂缓的理由**:
+
+1. **收益不明确**。无法确定 `acdbModelerSave/RestoreEntityFromDwgFiler` 比
+ `SAT 文本 → 临时文件 → acisIn` 快多少。目前能确认的收益只有一条:
+ **省掉临时文件,从而省掉杀软扫描时间**(用户对此有切身体会)。
+2. **而这条收益在 Phase 1 之后变得很小**。缓存落地后,落盘次数已从 O(实体数) 降到
+ O(唯一截面数)(几十次),杀软影响面同步缩小。
+3. **复杂度不低**:不能升版本号(否则老版本整实体变 proxy)、读端要延后构造否则会白读一遍 SAT、
+ DWG 体积翻倍、还要过一整套老版本兼容验证矩阵(附录 B)。
+4. **`region-sat-plan.md` §3.8 已确立"一条写路径"原则**,双写与之相悖,需要更强的收益理由。
+
+**结论**:先把缓存做掉,用 Phase 0/1 的数据说话。等有实测证明"modeler 通道比 SAT 通道显著更快"
+(或 DWG 体积成为独立诉求)时,再回到附录 B。
+
+---
+
+## 附录 A:SDK 能力边界(调研结论,备查)
+
+| 接口 | 位置 | R19.0 | R20.0+ |
+| --- | --- | --- | --- |
+| `AcDbBody::acisIn(const ACHAR* fileName, ...)` | `Arx/*/inc/dbBody.h:49` | 有(**仅文件名**,无 buffer 重载) | 有 |
+| `acdbModelerSaveEntityToDwgFiler` / `...ToDxfFiler` | `Arx/*/inc/dbacis.h` | **无** | 有 |
+| `acdbModelerRestoreEntityFromDwgFiler` / `...FromDxfFiler` | 同上 | **无** | 有 |
+| `acdbGetModelerDwgVer(dwgVer, pObj)` | 同上 | 无 | 有 |
+
+- `Arx/` 里搜不到 `api_restore_entity_list` 等 ACIS 底层 API → **想彻底不落盘,只能用 modeler filer 通道**。
+- modeler filer 的 `modelerVersion` 参数(`<0` 跟随 DWG、`0` 当前、`>0` 指定如 `2170`)与
+ `restoreOptions` 的 `kReadForeignEntities | kReadUnknownEntities`,是官方的**跨版本兼容机制**。
+- **坑**:`acdbModelerSaveEntityToDwgFiler` 要裸 `ENTITY*`,而 `AcDbBody` 只提供标
+ **INTERNAL USE ONLY** 的 `getLockedASMBody()` / `unlockASMBody()` / `setASMBody()`
+ (`Arx/R23.0/inc/dbBody.h:38-43`;R19 也有这些函数但没有对应的 save/restore)。
+ → 将来要用,必须先做 round-trip 最小验证(存/读后比对拓扑、体积、与 `acisIn` 结果一致性)。
+
+## 附录 B:将来若做"双写",必须注意的点
+
+### B.1 红线:绝对不能升 `kCurrentVersionNumber`
+
+```cpp
+// AecDbEwPipeBase.cpp:938
+if ( version > AecDbEwPipeBase::kCurrentVersionNumber )
+ return (Acad::eMakeMeProxy) ;
+```
+
+当前版本号 7。升到 8 → **老版本软件打开新图时整个实体变 proxy**,比"截面读不到"严重得多。
+做法应沿用 SAT 迁移时的既有模式(`nLen == 0` 作标志、不动版本号)。
+
+### B.2 逻辑漏洞:新软件会白读一遍 SAT
+
+当前流布局(`dwgOutFields:1343`):
+
+```
+1 父类字段 → 2 version=7 → 3 m_mapSpecXData → 4 m_pBaseCurve
+→ 5 ★region 块(writeUInt32(0) + SAT 文本)→ 6 m_arBreaks
+→ 7 m_nInfActive / GUID / 各种 Id → 8 WriteUserInterface() → 9 ← 新块只能加在这里
+```
+
+SAT 在**中间**、新块在**末尾** → 读到 SAT 时还不知道末尾有没有新块。
+若"先按 SAT 恢复、末尾再替换",等于每个截面多付一次"写盘 + acisIn",
+**双写反而更慢**。
+
+**修法**:延后的是**构造**,不是读取。
+
+- 读到 region 块时只把 SAT 文本读进内存(不落盘、不 `acisIn`)
+- 继续读完 6~8(需逐项确认不依赖 `m_pRegion`,尤其 `ReadUserInterface`)
+- 末尾尝试读新块:成功 → 用它构造 `m_pRegion` 并丢弃文本;失败 → 用文本走原路径
+
+### B.3 块格式建议
+
+```
+magic(8B) + blockVer(2B) + modelerVer(2B) + flags(4B) + len(4B) + payload
+```
+
+`blockVer` 是这个块自己的版本,与 `kCurrentVersionNumber` 无关;`len` 用于跳过与自愈。
+读端任何一步失败都**静默回退**(读不到 magic 属正常老数据,不能刷日志)。
+
+### B.4 必须验证的兼容矩阵
+
+- 老版本插件正常打开新图 / 外部参照 / 局部打开
+- 老版本 `AUDIT` / `RECOVER` 不报错、不删实体
+- 新图 `WBLOCK` / 复制粘贴到老图
+- 老版本打开后另存 → 新版本再打开(应回落 SAT,不报错)
+- 确认未触发 `eMakeMeProxy`
+- DXF 路径(`acdbModeler*ToDxfFiler`)同样过一遍
+- `copyFrom` / 深拷贝(内存 filer 走同一套 `dwgOut/dwgIn`)
+
+### B.5 代价
+
+| 代价 | 说明 |
+| --- | --- |
+| DWG 体积 | 几何存两份(SAT 文本 + 二进制),需实测单构件增量 × 实体数 |
+| 存盘时间 | `acisOut` 与 modeler save 各跑一遍 |
+| 打开 I/O | 流变长,即使跳过 SAT 也要读过去/跳过 |
+| 维护面 | 两套序列化都要测;读端优先级必须明确(新块优先、SAT 兜底) |
+| 老版本保存后 | 新块丢失 → 新版本回落 SAT,性能退化但不丢数据(需在文档中写明) |
+
+### B.6 覆盖范围
+
+`AecDbEwPipeBase` 是"region 块"结构,而 `AecDbEwEquipment` / `AecDbInsulation` 走 `StringToRegion`
+(文本存在普通字符串字段里),追加位置不同。建议先抽一对 helper 再推广:
+
+```cpp
+Acad::ErrorStatus WriteSectionModelerBlock(AcDbDwgFiler*, const AcDbRegion*);
+bool TryReadSectionModelerBlock(AcDbDwgFiler*, AcDbRegion*& out); // 失败返回 false,不报错
+```
+
+## 附录 C:与既有文档的关系
+
+- `AecDbEwPipeBase-region-sat-plan.md` §3.8:否决的是「**存储**按标准/异形分流」与「曲线通道写路径保留」。
+ 本计划的 Phase 1 是**运行期缓存**,不改存储,不违背该结论。
+- `GiDrawUtility-analysis.md` §7.4 E2:否决的是「同一 draw 内跨分段复用截面」,
+ 理由是"大头是 N−1 次 boolean unite"。本计划不再碰那个分支,而是把"造截面"整体次数降下来;
+ 同时提醒:**别指望截面缓存解决 Parasolid 的扫掠/布尔开销**,那是另一个 P0 项。
+- `temp-file-naming.md`:临时文件命名/多实例规范,Phase 2 改动需遵守。
+- `sqlite_query_cache.md`:同类问题(绘制期重复查库),已由 #87/#88 解决;
+ trace 实测 sqlite 从 17.0 % 降到 0.06 %,可作为"先测量、再缓存"的参考范例。