Merge pull request 'docs: 补开图速度优化方案' (#41) from company-desktop-computer/docs/open-speed-plan into main

Reviewed-on: #41
Reviewed-by: gjm <littleoldpan@163.com>
This commit was merged in pull request #41.
This commit is contained in:
gjm committed 2026-10-22 14:48:03 +08:00
commit b75b779518
1 file changed
+75 -7
+75 -7
View File
@@ -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