Files
envi-code/GiDrawUtility-analysis.md
T

151 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GiDrawUtility 分析:内存泄漏、效率与逻辑缺陷
> 目标:在动手改之前,把 `GiDrawUtility`(AecRxBase 公共几何工具库)的内存泄漏、性能热点和逻辑缺陷列清楚,给出可分批落地的修复顺序。
>
> 范围:`SourceCode/Code2026/AecRxBase/GiDrawUtility.cpp`(965 行)、`SourceCode/Code2026/inc/GiDrawUtility.h`。
> 关联:`EarThwormDef.h`(DELETE_PTR / EQUAL_ZERO 宏)、`inc/AdsXRx/tsuperlist.h`(DELETE_PTR 定义)。
>
> 状态(2026-10-25):仅分析,未改代码。行号以当前 main 为准。
## 1. 背景与影响面
GiDrawUtility 是全库共用的 3D 几何构建工具(DrawPipe / DrawLoft / DrawRevolve / DrawCone / DrawBox 等),以 `AECDLLIMPEXP` 导出,直接被 14+ 个模块调用:
- `DrawPipe(ptFrom, ptTo, dRadius, dThick, nSegs)`:`AecDbEwPipe`、`AecDbHvacDuct`、`AecDbECableTray`、`AecDbSludgePipe`、`EarthWormCmd/DGGLDlg.cpp`、`stakeOper.cpp`,以及所有 `DrawCylinder`(内部转调此函数);
- `DrawPipe(pCurve, pRegion, ptCenInRgn)`:`AecDbEwPipeBase`(约 20 处)、`AecDbEwFitting`(约 40 处)、`AecDbArcStair`、`AecDbLineStair`、`AecDbClimbStair`、`AecDbGuardBar`、`AecDbFlume` 等;
- `DrawPipe(pCurve, dR)`:`AecDb3dRoadAxisLine`、`AecDbEwFitting`。
因此本文件的问题会随"每画一个管件/实体"重复触发,泄漏与效率问题在连续布管、explode 分解场景下会持续累积。
约定说明:`DELETE_PTR(p)` 宏定义为 `if (p) { delete p; p = NULL; }`(`tsuperlist.h`),delete 后置 NULL,故"DELETE_PTR 后 return 该指针"不会产生野指针;未 DELETE_PTR 的失败路径才是泄漏。`booleanOper` 成功后第二个操作数不被消耗、需调用方手动释放——本仓库 `AecDbEwPipeBase.cpp`、`AecDbEwFitting.cpp` 均循此例。
## 2. 内存泄漏
### L1 严重·高频:DrawPipe 重复建 region 且丢弃第一次结果(GiDrawUtility.cpp:677-678)
```cpp
if (!createRegion(&Cir1)) return NULL; // 返回值(new AcDbRegion)被直接丢弃 → 泄漏
AcDbRegion *pBig = createRegion(&Cir1); // 同样的几何又建了一遍
```
`createRegion` 返回 `createFromCurves` 产出的堆对象,第一次调用**每次必漏一个 AcDbRegion**。所有画管道/圆柱的路径(含 DrawCylinder)都踩中,画一根管子漏一个 region。
**修法**:删掉第一行,改为
```cpp
AcDbRegion *pBig = createRegion(&Cir1);
if (!pBig) return NULL;
```
### L2 严重·同函数:pSml 两处泄漏(GiDrawUtility.cpp:684-700)
- `dThick > dRadius` 时内圆负半径,`createRegion(&Cir2)` 失败后 `return NULL`,`pBig` 未释放;
- `booleanOper` **成功路径** `pSml` 从未释放(失败路径反而删了)。按仓库惯例成功后应 `DELETE_PTR(pSml)`。
### L3 DrawLoft(Curve*,Curve*,Curve*) 失败路径漏三个 clone(GiDrawUtility.cpp:238)
```cpp
if (!pRegT || !pRegB) return NULL; // _pTop/_pBottom/_pLine 及已成功的 pRegT 全漏
```
`DrawPyramid` / `DrawTydf` / `DrawCirTorus` / `DrawRectTorus` 截面非 region 时走这条。
### L4 DrawBox / DrawSphere 失败路径漏 pSolid(GiDrawUtility.cpp:432、449)
`createBox` / `createSphere` 返回非 eOk(负尺寸等)时 `return NULL` 前未 delete。
### L5 DrawMesh 失败路径双重泄漏(GiDrawUtility.cpp:355-377)
`createLoftedSolid` 失败时 `DELETE_PTR(pEnt)` 后直接 return——`new AcGePoint3d[columns]`(pSection)**和** `vecPLs` 中已生成的所有 polyline 全部泄漏。成功路径反而清理完整,失败路径什么都没清。
### L6 DrawPipe(pCurve, dR) 两平台分支不对称(GiDrawUtility.cpp:820-829)
closed polyline 走 `createSweptSolid`:ZWCAD 分支(866 行起)失败时有 `DELETE_PTR(pReg)`,ARX 分支没有 → ARX 上扫掠失败即漏 `pReg`。
### L7 模式性问题:createFromCurves 只取 [0](GiDrawUtility.cpp:130-135、645-648、817-819)
`createRegion(AcDbEntity*)`、`DrawDish`、`DrawPipe(pCurve, dR)` 均只取结果数组第一个 region,多 region 返回(自交多边形等)时其余全漏。圆/简单闭合环一般返回 1 个,概率低但存在。
## 3. 运行效率
| # | 位置 | 问题 |
| --- | --- | --- |
| E1 | 677-678 | 即 L1:每次画管道多跑一次 `createFromCurves`(ACIS 级开销) |
| E2 | 830-860 | fallback 分段递归时**每段重新建同一个圆 region**(AcDbCircle + createFromCurves + sweep)。长路径(>100000)分十几段就重复十几遍。应段外建一次模板 region,段内 `clone() + transformBy()` |
| E3 | 145-160 | `transfromRegion` 对 region 做**两次** `transformBy`(先旋转后平移)。region 是 ACIS 拓扑,每次变换触发几何重算;两条矩阵可合成一次。每条管道热路径 |
| E4 | 466-467、553-555 | `DrawPyramid`/`DrawTydf` 中 `mat.setToTranslation(ptBottom - kOrigin)` 在 `ptBottom=原点` 时是恒等变换,白做一次 transformBy;顶点可直接用 3D 点 `addVertexAt` 完全免掉 |
| E5 | 455-460 | `DrawSphere` 用 `RotateAndMove(kZAxis, origin, kZAxis, ptCenter)`——同轴 rotateTo 返回单位阵,纯平移即可 |
## 4. 执行逻辑
### B1 逻辑错误:RotateAndMove 平移项不完整(GiDrawUtility.cpp:186)
```cpp
mat = _vtOrig.rotateTo(_vtDist); // p' = R·p
mat.setTranslation(ptDist - ptOrig); // p' = R·p + (ptDist - ptOrig) ← 错
```
把 ptOrig 搬到 ptDist 的正确平移项是 `ptDist - R·ptOrig`。当前实现仅在 ptOrig 为原点时正确:
- 现有内部调用恰好都传 `kOrigin`,暂未暴露;
- **DrawTydf 的 bIsTfdy=TRUE 分支**(508-518 行先把 ptBottom 平移到 (0,0,H),562 行再 `RotateAndMove(kZAxis, ptBottom, vtMove, ptPos)`)ptOrig 非原点——**vtMove 不是 Z 轴时台体落点偏移 R·ptOrig - ptOrig**,是当前代码中唯一实际踩中的路径;
- 该函数是 AECDLLIMPEXP 公开 API,未来调用方传非原点必错。
**修法**一行:
```cpp
AcGePoint3d ptTmp(ptOrig);
ptTmp.transformBy(mat);
mat.setTranslation(ptDist - ptTmp);
```
### B2 零向量防护是谎言(GiDrawUtility.cpp:180-183)
`_vtOrig *= 1; // 保证长度不为0` 是 no-op。输入零向量时 `rotateTo` 行为未定义,应显式判零并回退默认轴。
### B3 DrawTydf 的 tfdy 分支 guide 线未随截面变换(GiDrawUtility.cpp:508-521)
`line` 在变换**之前**构造;tfdy 分支把 `plBottom` 上移 H、`cir` 下移 H,`line` 原地不动,不再连接两截面中心。loft 的 guide rail 直接影响曲面形状;非 tfdy 分支的 line 是连接两截面中心的,行为不一致,疑似 bug(需确认是否有意为之)。
### B4 DrawMesh 无入参校验(GiDrawUtility.cpp:340-352)
- `pVertexList` 为 NULL 直接解引用;
- `rows * columns == 0` 时 `pVertexList[rows * columns - 1]` 越界读 [-1];
- `rows == 1` 时截面数组为空,`createLoftedSolid` 失败又回到 L5 泄漏路径;
- `first = 1, last = rows - 1` 中 rows 为 UInt32,rows=0 时 last 下溢为 -1(循环虽不执行,但上面 line 构造已先越界)。
### B5 DrawRevolve 失败时往图纸里塞红线(GiDrawUtility.cpp:283-286)
```cpp
if (!pReg) { _pC->setColorIndex(1); AddToModelSpace(_pC); return NULL; }
```
工具函数产生"往模型空间加实体"的副作用,调用方无法预期也无法关闭,疑似调试代码残留。且用的是匿名 namespace 里不判 `pEnt` 空的那个重载。
### 其余逻辑问题
| # | 位置 | 问题 |
| --- | --- | --- |
| B6 | 673 | `nSegs` 死参数:签名带分段数,函数体完全没用,调用方以为能控制精度 |
| B7 | 794 | `getStartPoint(ptS)` 返回值未检查,未定义点传入 `getFirstDeriv` |
| B8 | 138 | `create3DPolyline` 无条件 `makeClosed()`:开口折线被强制闭合,回转出闭合实体;`len<=0` 不校验 |
| B9 | 636 等 | 全局无参数校验:`DrawDish` 的 `2*dH/dD` 除零;`DrawCirTorus`/`DrawRectTorus` 在 `minorRadius >= majorRadius` 时构造负半径几何直接喂 ACIS;`ptFrom==ptTo` 零长路径扫掠 |
| B10 | 405-412 | `DrawExclude(pCurve, vtPath)` 用包围盒中心作拉伸起点,凹形截面时起点在区域外,需确认是否符合预期 |
| B11 | 10-84 | 匿名 namespace 里三个 `AddToModelSpace` 重载语义分裂:4 参版返回 ErrorStatus、2 参 bool 版返回 AcDbObjectId、3 参默认值版返回 ErrorStatus 且不判 `pEnt` 空,靠重载决议分流,维护陷阱 |
## 5. 规范问题
- **S1** `spliteCurve`(722 行)、`deleteContainerOfArx`(770 行)是文件级 `static`,违反 CODEBUDDY.md"文件内私有函数放匿名 namespace"约定——本文件已有匿名 namespace,应移入。
- **S2** `transfromRegion` 函数名拼写错误(transfrom → transform),公开 API 不宜直接改名,建议加正确拼写的 wrapper。
- **S3** `DELETE_PTR` 宏无 `do-while(0)` 包裹(`if (ptr) { delete ptr; ptr = NULL; }`),本文件现有用法均为独立语句暂无 bug,但与 if/else 搭配时是陷阱。
## 6. 建议修复顺序
1. **L1 + L2**:改动约 3 行,消掉最高频的泄漏(每画一个管件一次),收益最大;
2. **B1**:一行修复整类错位风险,需回归 DrawTydf 各分支及所有落点;
3. **L3 / L4 / L5 / L6**:失败路径按触发概率补清理;
4. **B4 / B9**:入参校验,防崩溃类;
5. 其余(B2/B3/B5~B8/B10/B11、E 系列、S 系列)按需。
注意事项:GiDrawUtility 是 AecRxBase 公共库、DrawPipe 系列是全库管道实体绘制的公共入口,改动后建议小步提交,编译对应 preset 并在 CAD 中实测(画管、台体、回转体、loft 分解)。