From f0bf3f8fe2758993536def7f5af4e4f46b053285 Mon Sep 17 00:00:00 2001 From: company-desktop-computer Date: Thu, 22 Oct 2026 14:46:18 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E5=BC=80=E5=9B=BE=E9=80=9F?= =?UTF-8?q?=E5=BA=A6=E4=BC=98=E5=8C=96=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第 13 节从「两个候选方向」扩写成可执行方案: - 13.1 成本模型:dwgInFields:1824 无条件 Create();Create() 每次全量重建, AddX3dSolid 不置脏,调用方只能靠再调 Create() 保证同步 - 13.2 方案 A(推荐,不改格式),分两步: A1 加 m_bDirty,Create() 早退,改参数的 15 个接口置脏 A2 dwgInFields 去掉 Create(),5 个读入口统一走 EnsureCreated 附收益边界与四个风险点(副作用延迟、失败重试、const_cast、CopyFrom 冗余) - 13.3 方案 B 缓存布尔结果:dwg 字段要升版本(不接受)、扩展字典 xrecord 可兼容但体积膨胀,结论是量完 A 再定 - 13.4 方案 C 降低单次重建成本:bbox 剔除、减少 clone、DrawUserInf3d 热点 - 13.5 度量埋点与验收判据 - 13.6 建议顺序:A1 → A2 → 再决定 B/C --- AcDbX3dSolid-refactor-plan.md | 82 ++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/AcDbX3dSolid-refactor-plan.md b/AcDbX3dSolid-refactor-plan.md index 95b47a6..7fee39d 100644 --- a/AcDbX3dSolid-refactor-plan.md +++ b/AcDbX3dSolid-refactor-plan.md @@ -559,17 +559,85 @@ void SaveLegacyDwg() --- -## 13. 下一步方向:开图速度(2026-10-22 记录) +## 13. 开图速度优化方案(2026-10-22) -阶段三取消后,性能问题回到开图本身。已核对的瓶颈: +### 13.1 现状与成本模型 - `AecDbX3dPartEntity::dwgInFields:1824` 在开图时无条件调用 `m_pPart->Create()`。全库扫描 `dwgInFields`,立即重建的只有这一处 -- `AecDbX3dPart::Create()`(`AecDbX3dPart.cpp:786`)对每个 unite 成员调 `GetDraw3dSolid()`(`EnsureCreated` + `clone` + 旋转),逐个 `booleanOper(kBoolUnite)`,再跑 subtract 列表,最后整体变换 +- `AecDbX3dPart::Create()`(`:786`)先删掉 `m_pSolid`,再对每个 unite 成员调 `GetDraw3dSolid()`(`EnsureCreated` + `clone` + 旋转),逐个 `booleanOper(kBoolUnite)`,再跑 subtract 列表,最后整体旋转/平移、设颜色、把 `m_mapXData` 写成 `BIMDATA` XData - 单个 part 的成本约 N×(acisIn + clone) + (N−1)×布尔。N 大、布尔对象复杂时是非线性增长,这正是「复杂几何由 boolean 得到」的场景 +- `Create()` **没有「已是最新」判断**,每次调用都全量重建;`AddX3dSolid`(`:151`)只 append、不置脏,所以调用方只能靠再调一次 `Create()` 保证几何同步 -两个候选方向: +### 13.2 方案 A:脏标记 + 懒创建(推荐,不改存储格式) -1. **懒执行**:把 `X3dPartEntity::dwgInFields` 里的 `Create()` 去掉,改成首次绘制或取几何时再建(同阶段二的 `m_bNeedCreate` 模式)。改动小、风险低;收益集中在关闭/冻结图层、视口外、只读打开(XREF、审核、发布)。全图都在视口内时,只是把成本从开图移到首次显示 -2. **缓存布尔结果**:把 part 最终的 solid 以 SAT 存进 DWG,开图时一次 `acisIn` 代替 N 次建体 + N 次布尔。这是唯一真正减少计算量的办法,代价是体积增大、参数变更要失效重建、`Create()` 末尾挂在 solid 上的 `BIMDATA` XData 要重新挂 +分两步,可以分开提交。 -方向未定,待决策。 +**A1 让 `Create()` 幂等** + +| 位置 | 改动 | +| --- | --- | +| `inc/AecDbX3dPart.h:140` 附近 | 增加 `bool m_bDirty;`,构造(`:50`)置 `true` | +| `Create()` `:786` | 开头 `if (!m_bDirty && m_pSolid != NULL) return true;`;进入构建前先 `m_bDirty = false`,写法同 `AcDbX3dSolid::EnsureCreated`(`:4620`),避免重入时重复构建 | +| 改参数的接口 | 置 `m_bDirty = true`:`AddX3dSolid:151`、`RemoveAt:504`、`RemoveAll3dSolidRec:468`、`SetOrgPoint:170`、`SetAxis:582`、`SetBoolOperFor:766`、`SetHideShow:602`、`SetHide:608`、`SetActive:639`、`PeelOffTheSkin:623`、`AddMove:644`/`:655`、`ResetMove:671`、`AddRotate:682`、`ResetRotate:705`、`CopyFrom:1255` | +| 新增 | `bool EnsureCreated() const`:`m_bDirty` 时 `const_cast` 调 `Create()` | + +现状里 `AddMove`、`AddRotate` 等已经 `DELETE_PTR(m_pSolid)` 做失效,改成置脏后语义一致,额外好处是能区分「已失效但还没人要」和「已重建」。 + +**A2 开图不建几何** + +- `AecDbX3dPartEntity::dwgInFields:1824` 去掉 `m_pPart->Create()`,由 `AecDbX3dPart::dwgInFields` 结束时置脏 +- 读入口统一先 `EnsureCreated()`: + +| 入口 | 位置 | +| --- | --- | +| `CreateDraw3dSolid()` | `AecDbX3dPart.cpp:940` | +| `Draw3d()` | `:969` | +| `getOsnapPoints()` | `:1097` | +| `GetForHouseSubtract()` | `:903` | +| `AecDbX3dPartEntity::Draw3d()` | `AecDbX3dPartEntity.cpp:1755`,经 `CreateDraw3dSolid` 已覆盖,确认即可 | + +`AecDbBDPipeBase.cpp:329` 那种「`CreateDraw3dSolid()` 返回空就 `Create()` 再试」的兜底可以删掉,`EnsureCreated` 已经包含在 `CreateDraw3dSolid` 里。 + +**收益边界** + +- 关闭/冻结图层、视口外、XREF 附着、审核、发布等不绘制的场景:完全省掉 +- 全图都在视口内:开图时间下降,首次 regen 补回来,总时间基本持平 +- A1 单独也有收益:所有「改完参数就 `Create()`」的交互路径不再重复全量重建 + +**风险** + +- `Create()` 有副作用:整体变换、`setColorIndex`、`m_mapXData.Write(m_pSolid, "BIMDATA")`(`:865-898`)。延迟后这些也延迟,凡是在建好之前读 solid 的代码都必须经过 `EnsureCreated` +- unite 列表为空时 `Create()` 返回 false 且 `m_pSolid == NULL`;`DTYG_TYPE` 且 `Hide` 的分支直接 `return true` 且不建体(`:792-799`)。`EnsureCreated` 的判定只看 `m_bDirty`,不能因为 `m_pSolid == NULL` 反复重试 +- `Create()` 非 const,`EnsureCreated` 需要 `const_cast` +- `CopyFrom:1255` 里对每个 x3dsolid 调了 `ReCreate()`,可以一起删掉——`AcDbX3dSolid` 已有 `m_bNeedCreate`,懒加载会处理 + +### 13.3 方案 B:缓存布尔结果(改格式,暂不推荐) + +把 part 最终的 solid 用 `acisOut` 存成 SAT,开图时一次 `acisIn` 代替 N 次建体 + N 次布尔。这是唯一真正减少计算量的办法,但两条落点都有代价: + +| 落点 | 兼容性 | 问题 | +| --- | --- | --- | +| `AecDbX3dPart` 的 dwg 字段 | 需要升版本号,老插件整块变 proxy | 与阶段三取消的理由相同,不接受 | +| 实体扩展字典(xrecord 分块存 SAT) | 老插件忽略未知 xrecord,仍按参数重建 | 保存/打开后写缓存、失效判定、体积膨胀都要额外处理 | + +体积上,布尔结果的 SAT 通常比输入参数之和大一个量级,复杂 part 可能到几百 KB,会打破「图纸大小可接受」这个前提。 + +结论:先做 A,量出开图耗时里布尔占多少,再决定 B 值不值。 + +### 13.4 方案 C:降低单次重建成本(可选) + +- 合并前先按 bbox 剔除被完全包含的成员,少跑几次布尔 +- `GetDraw3dSolid()` 每个成员 clone 一次完整 solid,N 个成员就是 N 次深拷贝;只参与一次布尔的成员可以改成「取原生指针直接参与运算」,但要逐个确认所有权(`booleanOper` 会消耗传入对象) +- `AecDbECFitting::DrawUserInf3d`(`AecDbECFitting.cpp:4001`)每次绘制都新建临时 `AecDbX3dPart` 并 `Create()`,和持久化对象无关,属于另一个热点 + +### 13.5 度量与验收 + +- 埋点:`AecDbX3dPart::Create()` 的调用次数与累计耗时、开图总时长、首次 regen 时长 +- 场景:全图可见 / 一半图层冻结 / 缩放到局部 / 作为 XREF 附着 +- 判据:开图时长下降;首次 regen 的增量不超过开图省下的量;移动、改参数等交互不退化 + +### 13.6 建议顺序 + +1. A1(`m_bDirty` + `Create()` 早退):改动小,先量交互路径的收益 +2. A2(`dwgInFields` 去掉 `Create()`):开图收益 +3. 量完再决定 B / C