From cad5289fae1d706e4348c9e8dadcc1513882b9bb Mon Sep 17 00:00:00 2001 From: company-desktop-computer Date: Thu, 22 Oct 2026 13:26:51 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E9=98=B6=E6=AE=B5=E4=B8=89?= =?UTF-8?q?=E7=9A=84=E9=99=8D=E7=BA=A7=E5=AF=BC=E5=87=BA=E6=96=B9=E6=A1=88?= =?UTF-8?q?=EF=BC=88=E6=96=B0=E6=A0=BC=E5=BC=8F=20=E2=86=92=20=E8=80=81?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增第 12 节。核心结论:降级不需要重建几何,容器里就是 acisOut 的 SAT ASCII 字节,老 m_sScr 里也是同一批字节,解包后按老规则拼回即可,纯内存、 不调 acisIn、不落临时文件。 - 在 dwgOutFields 里做:AcDbX3dSolid 的持有关系分散在 X3dPart、X3dPartEntity 以及 Hanger/Roof/Stair 的 m_solids 缓存里,命令层遍历不全,但存盘时每个 实例必然走 dwgOutFields - 进程级开关 + RAII 复位,只改写出内容,不改对象状态,导出后当前图仍是新格式 - MakeLegacyScr 按 3.2 的表分四类拼回,容器写出长度 0 - 12.6 指出 4.3.6「region 类型停写 m_sScr」与 kIntersect/kSliceSolid 的折线 后缀冲突,建议这两种类型继续只写折线后缀 - 12.8 记录限制:导出写的对象版本号是 4,只能被 kCurrentVersionNumber >= 4 的插件读取;版本号 3 的存量插件靠降级救不回来,需要另写 v3 布局 11.1、11.2、11.4 同步加交叉引用与状态行。 --- AcDbX3dSolid-refactor-plan.md | 130 ++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/AcDbX3dSolid-refactor-plan.md b/AcDbX3dSolid-refactor-plan.md index 69cef12..ea1edf7 100644 --- a/AcDbX3dSolid-refactor-plan.md +++ b/AcDbX3dSolid-refactor-plan.md @@ -385,6 +385,8 @@ void SetSweepData(const CString& sReg, const AcDbCurve* pCurve); 不可见不等于丢数据:`m_pSatBuffer` 老版本也会原样读写(版本 > 1 的通道一直都在),所以旧版打开后另存、再回新版打开,几何仍能恢复。非 region 类型(`m_sszPoint2ds` 点串、SubDMesh、管廊脚本)不受影响。 +需要把图交给老版本用户时,用第 12 节的降级导出:存盘瞬间把容器翻成老格式的 `m_sScr`,老插件就能正常显示几何,当前文档本身不受影响。 + | 选项 | 后果 | 结论 | | --- | --- | --- | | 不升版本号,接受老插件缺几何 | 老插件能开图、参数都在,region 类型几何不显示;换新版可恢复 | **已定,选这个** | @@ -395,6 +397,7 @@ void SetSweepData(const CString& sReg, const AcDbCurve* pCurve); 1. `TransformBy`、`ToJson` 是否一起改——这两处在运行时会反复导出/解析 SAT,是阶段三改动量的大头 2. 老图纸里 region 类型的实际占比——占比低的话体积收益有限,可以永久搁置 +3. `kIntersect` / `kSliceSolid` 的折线后缀怎么存(12.6)——阶段三动 `m_sScr` 之前必须先定,否则折线文本会丢 ### 11.3 实施时的技术前提(已核对) @@ -411,3 +414,130 @@ void SetSweepData(const CString& sReg, const AcDbCurve* pCurve); | 一:临时文件唯一化 | 已合并 | #22、#26 | | 二:懒重建 | 已合并 | #25、#27 | | 三:region 二进制化 | 暂缓 | — | +| 降级导出(阶段三配套) | 方案已定,未实施 | — | + +--- + +## 12. 降级导出(新格式 → 老格式) + +### 12.1 结论 + +可行,而且不需要重建几何。容器里存的就是 `acisOut` 出来的 SAT ASCII 字节,老 `m_sScr` 里存的也是同一批字节;降级只是「解包容器 → 按老规则拼字符串」,纯内存操作,不调 `acisIn`、不落临时文件、不损失任何东西。 + +这让阶段三的兼容代价从「老版本永远看不到新图几何」变成「平时用新格式,交付给老版本用户前导一次」。 + +### 12.2 为什么在 `dwgOutFields` 里做 + +`AcDbX3dSolid` 的持有关系是分散的:`AecDbX3dPart`、`AecDbX3dPartEntity`,以及 `AecDbHanger` / `AecDbRoof` / `AecDbStair` 各自的 `m_solids` 缓存。从命令层遍历找不全所有实例,但存盘时每个实例一定会走 `dwgOutFields`。 + +所以在序列化层加一个进程级开关:开关打开时,`dwgOutFields` 把 region 容器翻译成老格式的 `m_sScr`,并抑制容器写出。 + +### 12.3 接口 + +```cpp +// AcDbX3dSolid.h +static void SetLegacyOutput(bool b); +static bool IsLegacyOutput(); + +private: +bool MakeLegacyScr(CString& sScr) const; // 解包容器,按 m_eType 拼回老格式 +bool HasNewRegionData() const; // 见 4.3.4 +``` + +`MakeLegacyScr` 成功返回 true;容器损坏、解包不出 region 时返回 false,调用方退回写新格式(宁可不降级,也不写出空几何)。 + +### 12.4 `dwgOutFields` 的改动 + +只有两处:`:4797` 写 `m_sScr`、`:4822-4829` 写容器。 + +```cpp +CString sScrOut = m_sScr; +const void* pSatOut = m_pSatBuffer; +Adesk::UInt32 nSatOut = m_nSatLen; + +if (IsLegacyOutput() && HasNewRegionData() && MakeLegacyScr(sScrOut)) +{ + pSatOut = NULL; // 容器不写出,老插件读到长度 0 + nSatOut = 0; +} + +pFiler->writeString((LPCTSTR)sScrOut); +... +pFiler->writeUInt32(nSatOut); +if (pSatOut != NULL) + pFiler->writeBytes(pSatOut, nSatOut); +``` + +关键点: + +- **不改对象状态**。`m_sScr` / `m_pSatBuffer` 都不动,只换写出的内容,导出后当前图纸仍是新格式,可以继续编辑、继续存盘 +- 容器写 0 长度,老插件 `readUInt32` 读到 0 直接跳过,不会报错 +- 对象版本号仍写 4,不升 + +### 12.5 `MakeLegacyScr` 的拼法(按 3.2 的表) + +| 类型 | 容器里 | 拼回 `m_sScr` | +| --- | --- | --- | +| `kExtrude2` / `kSlicePipe` / `kRevolve` / `kRevolve2` / `kSweep` | 1 个 region | region 的 SAT 文本 | +| `kConePipe` | 2 个 region(外、内) | `外环SAT` + `\|` + `内环SAT` | +| `kIntersect` / `kSliceSolid` | 1 个 region | `regionSAT` + `m_sScr`(折线后缀,见 12.6) | +| `kLoft` | N 个 region | N 段 SAT 用 `@region@` 连接 | + +读端能容忍 `|` 留在第二段:`kConePipe` 用 `StringToRegion(sScr2)`(`:4489`)、`kIntersect` / `kSliceSolid` 用 `StringToPolyline(sScr2)`(`:4403`、`:4441`),两者内部都靠找 ACIS 头 / 数值容错。 + +### 12.6 阶段三必须先把折线后缀的位置定下来 + +`kIntersect` / `kSliceSolid` 的老格式是 `regionSAT|折线文本`,折线文本不是 region,不进容器。而 4.3.6 要求 region 类型停写 `m_sScr`——这两条冲突。 + +**建议**:这两种类型继续写 `m_sScr`,但只写老格式的第二段(含开头的 `|`,即原来的 `sScr2`)。理由: + +- 体积上只多了折线文本,region 那部分照常减半 +- 读端 `sScr2 = m_sScr.Mid(nPos)` 的既有切分逻辑不用改 +- 降级导出直接 `regionSAT + m_sScr`,不需要额外字段 + +不这么做的话,折线只能塞进容器做第二个 section,容器格式要加类型标签,复杂度和风险都上一个台阶。 + +### 12.7 命令 + +```cpp +void SaveLegacyDwg() +{ + AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase(); + // 目标文件:当前图名 + _legacy.dwg,或让用户选 + ScopedLegacyOutput guard; // 构造时置位,析构复位 + Acad::ErrorStatus es = pDb->saveAs(szTarget, false, AcDb::kDHL_CURRENT); + if (es != Acad::eOk) + acutPrintf(_T("\n导出失败,err=%d"), es); +} +``` + +- `saveAs(file, false, ...)` 的第二个参数是 `bBakAndRename`,传 `false` 只写副本、不切换当前文档(`dbmain.h:473`) +- 开关用 RAII 复位,命令异常、用户取消都不会留下「后续存盘全变老格式」的状态 +- 只处理当前数据库,外部参照里的 `AcDbX3dSolid` 不在范围内 + +### 12.8 一个必须说清的限制:只能兼容 `kCurrentVersionNumber >= 4` 的插件 + +`dwgOutFields` 写出的对象版本号固定是 4(`:4776`)。老插件的 `dwgInFields` 一旦发现 `version > kCurrentVersionNumber` 就返回 `eMakeMeProxy`(`:4682`),整个对象变代理,降级内容一点都读不到。 + +`dwgInFields` 里还留着 `version > 2 && version < 4` 的分支(`:4759`),说明线上确实存在版本号 3 的插件。**这些插件靠降级导出救不回来**——要兼容它们得额外写 v3 布局(`kSweep` 的路径要以 polyline 内联在对象字段之后),工作量另算,本次不做。 + +降级导出的目标客户是「版本号已经是 4、只是不认识容器」的插件。 + +### 12.9 验证清单 + +- 9 种类型各建一个样本,导出后装到老版本插件里打开,几何、面积、位置都正确 +- 老版本打开导出的图 → 另存 → 回新版打开,几何仍正确(回环) +- 导出前后,当前文档自身存盘仍是新格式(开关已复位) +- 容器被人为改坏(magic 对、region 段截断)时,`MakeLegacyScr` 返回 false,导出退回新格式并打日志,不产出空几何 +- `kIntersect` / `kSliceSolid` 的折线后缀原样保留,读回后截面边界正确 + +### 12.10 成本与时机 + +| 项 | 量 | +| --- | --- | +| 开关 + `dwgOutFields` 分支 | ~30 行 | +| `MakeLegacyScr` 按类型拼回 | ~100 行 | +| 导出命令 + 文件选择 | ~40 行 | +| 主要风险 | 老格式规则核对不全,导出的图在老版本里读不出来 | + +依赖 12.6 的折线后缀方案先定。与阶段三的先后关系:两者最好一起上——阶段三一旦合入、`m_sScr` 停写,没有降级导出的那段时间里,新图在老版本插件上就是缺几何的。