fix: 修复 AecDbEwEquipment 的 10 处逻辑缺陷 #52

Merged
gjm merged 1 commits from company-desktop-computer/fix/ewequipment-logic AGit into main 2026-10-25 11:42:31 +08:00
2 changed files with 142 additions and 47 deletions

No files matched your search

+65 -30
View File
@@ -302,7 +302,7 @@ void dwgOutFieldsForXPrim(AecEqui::XPrimData *pSolid, ...) // :239
顺带处理:跳过 `NULL` 指针并单独计数,避免"写了数量但循环少写一条"导致读写错位;删掉原来定义后未使用的 `int num`。
### 3.2 P1:`subErase` 里 `Find` 当布尔用
### 3.2 P1:`subErase` 里 `Find` 当布尔用(已修复)
```cpp
if (eSubType == eValve || sName.Find(_T("阀")) != -1 || sName.Find(_T("接头"))) // :2119
@@ -310,7 +310,9 @@ if (eSubType == eValve || sName.Find(_T("阀")) != -1 || sName.Find(_T("接头")
`CString::Find` 找不到返回 `-1`,非 0 即真,所以**第三个条件恒真**。整句等价于"永远是阀门",删除任何设备都会走合并管线那段逻辑(`GetInterface` + `PushGripMessage`)。应写成 `!= -1`。
### 3.3 P1:`DrawHandFlg` 用 `==` 赋值
**已修复**:补 `!= -1`。删除普通设备不再误走阀门/接头的合并管线分支。
### 3.3 P1:`DrawHandFlg` 用 `==` 赋值(已修复)
```cpp
if (nVt == 1) sFlag = _T("M");
@@ -320,7 +322,9 @@ else sFlag == _T("Y"); // :2656 应为 =
液动(3)时文字为空,画出来是空标注。另外这里直接取 `GetInterface(0)` / `GetInterface(1)`,接口不足 2 个时 `inf2` 是默认值,`distanceTo` 为 0、`normalize()` 得到零向量,后续 `angleTo` 结果不可信。
### 3.4 P1:`ProcessHlrResult` 在遍历中删 map 元素
**已修复**:`==` 改为 `=`,液动阀(3)现在会画出 `Y`。接口不足 2 个时 `inf2` 取默认值、方向不可信的问题未动,属于显示容错,另行评估。
### 3.4 P1:`ProcessHlrResult` 在遍历中删 map 元素(已修复)
```cpp
for (; it != _preAr.end(); ++it)
@@ -332,7 +336,9 @@ for (; it != _preAr.end(); ++it)
应改为 `it = _preAr.erase(it);`。这是典型的偶发崩溃点(容器元素少时不容易复现)。
### 3.5 P1:`GetSectionDraw` 返回值取反
**已修复**:改成 `while` + `erase(it++)`。没有用 `it = erase(it)`,因为 R190 走的是 C++98 标准(`cmakelists.txt` 按 SDK 版本切换),C++03 的 `map::erase(iterator)` 返回 `void`。
### 3.5 P1:`GetSectionDraw` 返回值取反(已修复)
```cpp
int nCount = arRegionPtrs.length();
@@ -343,6 +349,10 @@ return (nCount - nCoundPre); // :12762 顺序反了
返回的是"调用前 − 调用后",恒为 0 或负数。若调用方用它判断"是否剖到东西",判断必然失败。
**已修复**:改为 `nCoundPre - nCount`。全库调用点都忽略这个返回值,所以此前没有实际故障。
注意:`AecDbEwFitting.cpp:16523`、`AecDbECFitting.cpp:8751` 的同名函数是一模一样的写法(同样的错误),本次未改,见 5.4。
### 3.6 P1:`HLR_CALC` 标志会被存进图纸
`Draw3d:3009` 一旦读到 `HLR_CALC` 为真就直接返回,什么都不画。这个标志由 `PreprocessForHlr:13204` / `:13360` 置真、`ProcessHlrResult:13107` 复位。
@@ -360,11 +370,13 @@ return (nCount - nCoundPre); // :12762 顺序反了
米制图纸里,同一份数据经两个入口读出来的接口尺寸差 1000 倍。`Draw*Vlv2d` 用下标版,`subErase` / `PreprocessForHlr` 用数组版,两边算出来的东西对不上。
### 3.8 P1:`copyFrom` 漏拷字段
### 3.8 P1:`copyFrom` 漏拷字段(已修复)
`:2174` 只拷了 `m_mapSpecXData` / `m_nInfActive` / `m_ptInsert` / `m_vtDir` / 几何单元,**没有拷 `m_bVert`、`m_idDash`、`m_idCenter`**。复制粘贴一个竖放阀门,粘贴出来是横放的;两个图层软指针也丢了(虚线和中心线会跑到默认图层)。
### 3.9 P2:`MakeBlockRefSet` 的范围累加逻辑错
**已修复**:补拷 `m_bVert` / `m_idDash` / `m_idCenter`。
### 3.9 P2:`MakeBlockRefSet` 的范围累加逻辑错(已修复)
```cpp
for (...) {
@@ -382,7 +394,9 @@ for (...) {
应改成 `if (isValidExtents(extAdd)) ext.addExt(extAdd);`,块参照分支直接 `continue`。
### 3.10 P2:`byJson` 的 `m_pData` 尺寸对不上
**已修复**:块参照直接 `continue`;`extAdd` 每轮重新构造并检查 `getGeomExtents` 返回值;判断对象改为 `extAdd`。
### 3.10 P2:`byJson` 的 `m_pData` 尺寸对不上(已修复)
```cpp
p->m_nSize = it->value.GetInt(); // 字节数(来自 __nSize__)
@@ -393,16 +407,20 @@ p->m_pData = new PVOID[br._len]; // 按 PVOID 个数分配
分配长度取决于 `br._len`,使用长度取决于 `m_nSize`,两者来自不同字段。JSON 被改过或版本不一致时就会越界读写。应按 `m_nSize` 分配并校验 `sizeof(PVOID)*br._len >= m_nSize`。
### 3.11 P2:开图读入没有边界校验
**已修复**:`br._len` 本来就是字节数,改为 `new byte[br._len]` + `memcpy(..., br._len)`,并用 `br._len` 覆盖 `m_nSize`(两者不一致时以实际数据长度为准)。
### 3.11 P2:开图读入没有边界校验(已修复)
`dwgInFieldsForXPrim:172`:
- `readInt32(&nSize)` 后直接 `new byte[pSolid->m_nSize]`(`:193`),负值或超大值都没有检查(负值转 `size_t` 会申请巨大内存)
- `readInt32(&nSize)` 后直接 `new byte[pSolid->m_nSize]`(`:193`),超大值没有检查(负值被 `> 0` 挡住,不会分配)
- `readBytes` 的返回值没有检查
- `m_Combins` 是**递归**读(`:227`),没有深度上限,损坏的图纸可以打爆栈
同类的 `AcDbX3dSolid` 阶段一、二已经补过类似的防护,这里可以照搬。
**已修复**:加 16MB 上限(`kMaxXPrimDataSize`),超限 `setFilerStatus(Acad::eInvalidInput)` 让整个 `dwgInFields` 退出;`readBytes` 检查返回值,失败时释放并清零。`m_Combins` 的递归深度上限未加,损坏图纸仍可能打爆栈,优先级低。
### 3.12 P2:`PreprocessForHlr` 的"对面"被变换了两次
```cpp
@@ -417,14 +435,18 @@ for (...) {
原实体在 `-H/2` 位置,克隆体被移到 `+H` 位置(两次 `+H/2`),两侧不对称。除非这是刻意做的"厚度补偿",否则应只乘一次。建议拿一个剖切样例对照确认。
### 3.13 P2:`GetBodyBox` 恒返回 `TRUE`
### 3.13 P2:`GetBodyBox` 恒返回 `TRUE`(已修复)
`:4205` 无论有没有取到实体都 `return TRUE`;且 `ar3D` 引用在 `Draw3d` 之前就绑定了(`:4207`)。当前 `Get3DBuffer()` 返回成员引用,重新填充不会换对象,所以能工作;但如果哪天改成返回副本就会静默失效。返回值也应反映实际结果。
### 3.14 P2:`clearGeoBuffer()` 是空实现
**已修复**:返回实际是否取到范围;`_ext` 改为每轮新建,避免把上一轮的累积结果重复并入。11 处调用点都忽略返回值,行为不变。
### 3.14 P2:`clearGeoBuffer()` 是空实现(已修复)
`:12861` 函数体只有 `AssertWriteEnabled();`。`PipelineCmd/EquipmentMakeDlg.cpp` 有 8 处调用,本意应该是清掉 2D/3D 缓存(`Clean2DBuffer` / `Clean3DBuffer`)。现在是"调了没反应",改了参数后旧几何还在。
**已修复**:改为 `Clean2DBuffer(); Clean3DBuffer();`。调用方(`EquipmentMakeDlg`、`EQU_MODIFY`、`ParamEquJson`)都是改完参数后调它,语义一致。
### 3.15 P2:`booleanOper` 的所有权写法在本文件里自相矛盾
同一文件里两种写法并存:
@@ -539,21 +561,25 @@ Draw3dSolid(&dc, nColor); // :3062 拖拽时每帧都走到这里
### 5.1 第一批:内存与正确性(不改接口、不改格式)
拆成两批提交:**内存问题已做(见 2.13)**,逻辑缺陷待做。
分两批提交:**内存问题已完成(见 2.13)**,**逻辑缺陷已完成(见 3.2~3.14)**。
| # | 改动 | 位置 | 风险 | 状态 |
| --- | --- | --- | --- | --- |
| 1 | 删掉 `dwgInFields` 里的重复分配,`readString` 补 `acutDelString` | `:1707` `:1720` | 低 | 已完成 |
| 2 | 析构补 `FreeSubPrimData()` | `:1686` | 低(依赖 #3) | 已完成 |
| 3 | `releaseData()` 统一 `delete[]`,`FreeSubPrimData()` 释放 `m_pData` 后删结构体 | `:1520` | 低 | 已完成(见 2.3、2.13) |
| 4 | `subDMesh2NewSolid` 按下标倒序删除并释放 | `:3159` | 中(涉及几何正确性,需样例图) | 已完成 |
| 5 | `RemoveSubPrimObj*` 补释放 `m_pData` | `:4276` `:4291` | 低 | 已完成 |
| 6 | `subErase` 的 `Find` 补 `!= -1` | `:2119` | 低(行为会变,需回归删除阀门) | 待做 |
| 7 | `DrawHandFlg` 的 `==` 改 `=` | `:2656` | 低 | 待做 |
| 8 | `ProcessHlrResult` 改用 `it = erase(it)` | `:13128` | 低 | 待做 |
| 9 | `copyFrom` 补 `m_bVert` / `m_idDash` / `m_idCenter` | `:2174` | 低 | 待做 |
| 10 | `GetSectionDraw` 返回值改 `nCoundPre - nCount` | `:12762` | 低(需确认调用方语义) | 待做 |
| 11 | `clearGeoBuffer` 实现为清 2D/3D 缓存 | `:12861` | 低 | 待做 |
| # | 改动 | 风险 | 状态 |
| --- | --- | --- | --- |
| 1 | 删掉 `dwgInFields` 里的重复分配,`readString` 补 `acutDelString` | 低 | 已完成 |
| 2 | 析构补 `FreeSubPrimData()` | 低 | 已完成 |
| 3 | `releaseData()` 统一 `delete[]`,`FreeSubPrimData()` 释放 `m_pData` 后删结构体 | 低 | 已完成(见 2.3、2.13) |
| 4 | `subDMesh2NewSolid` 按下标倒序删除并释放 | 中 | 已完成 |
| 5 | `RemoveSubPrimObj*` 补释放 `m_pData` | 低 | 已完成 |
| 6 | `subErase` 的 `Find` 补 `!= -1` | 低(行为会变) | 已完成 |
| 7 | `DrawHandFlg` 的 `==` 改 `=` | 低 | 已完成 |
| 8 | `ProcessHlrResult` 改用 `while` + `erase(it++)` | 低 | 已完成 |
| 9 | `copyFrom` 补 `m_bVert` / `m_idDash` / `m_idCenter` | 低 | 已完成 |
| 10 | `GetSectionDraw` 返回值改 `nCoundPre - nCount` | 低 | 已完成 |
| 11 | `clearGeoBuffer` 实现为清 2D/3D 缓存 | 中(影响所有改参数路径) | 已完成 |
| 12 | `MakeBlockRefSet` 范围累加改判 `extAdd` | 低 | 已完成 |
| 13 | `byJson` 按字节长度分配 `m_pData` | 低 | 已完成 |
| 14 | `dwgInFieldsForXPrim` 加尺寸上限与读失败处理 | 低 | 已完成 |
| 15 | `GetBodyBox` 返回实际结果 | 低 | 已完成 |
另外补做的 4 处内存修复(2.6、2.7、2.9、2.10、2.11、`GetDraw3dables` 合并泄漏、`dwgInFields` 的 `RemoveAll`)见 2.13。
@@ -581,9 +607,12 @@ Draw3dSolid(&dc, nColor); // :3062 拖拽时每帧都走到这里
### 5.4 需要先确认的事
1. ~~`booleanOper` 是否删除入参(3.15)~~ —— 2026-10-23 按「不删除入参」处理,理由见 3.15
2. `GetSectionDraw` 的返回值调用方到底期望什么(3.5)
3. `PreprocessForHlr` 的 `mat4` 双次变换是否刻意(3.12)
4. ~~`kCombination` 单元的实际存量(5.2)~~ —— 2026-10-25 确认仍在用,见 3.1
2. ~~`GetSectionDraw` 的返回值调用方到底期望什么(3.5)~~ —— 2026-10-25 核实全库调用点都忽略返回值,已按「返回新增个数」修正
3. ~~`kCombination` 单元的实际存量(5.2)~~ —— 2026-10-25 确认仍在用,见 3.1
4. `PreprocessForHlr` 的 `mat4` 双次变换是否刻意(3.12)
5. `HLR_CALC` 是否从 `m_mapSpecXData` 的序列化里排除(3.6):要动 `AecDbObjXDataMap` 公共类,且 `AecDbEwPipe` / `AecDbEwFitting` / `AecDbOpening` / `AecDbPipeHanger` 都在用它,需逐个确认有没有依赖它跨会话保持的地方
6. `GetInterface` 数组版是否补 `dUnit`(3.7):补了之后米制图纸里所有走数组版的尺寸会放大 1000 倍,需确认 `subErase`、`PreprocessForHlr`、`Draw*Vlv2d` 的下游计算不受影响
7. `AecDbEwFitting.cpp:16523` / `AecDbECFitting.cpp:8751` 的 `GetSectionDraw` 同样写反,是否一起改
---
@@ -599,6 +628,12 @@ Draw3dSolid(&dc, nColor); // :3062 拖拽时每帧都走到这里
| `subErase` | 删除阀门/接头/普通设备各一次,确认只有阀门走合并管线 |
| `HLR_CALC` | 剖切过程中保存再重开,设备 3D 仍可见 |
| `copyFrom` | 复制粘贴竖放阀门,方向与图层保持 |
| `DrawHandFlg` | 液动阀(驱动类型 3)2D 符号旁显示 `Y` |
| `ProcessHlrResult` | 反复开关阀门剖切 2D 显示,不崩溃 |
| `clearGeoBuffer` | 改单元参数(box/cylinder/cone)后关闭对话框,几何立即更新 |
| `MakeBlockRefSet` | 剖切/2D 导出生成的块,插入点位于设备中心而不是原点 |
| `byJson` | JSON 导入设备,几何与参数正确 |
| `subErase` 的 `Find` | 删除普通设备后,相邻管线不被合并 |
| 拖拽优化 | 拖夹点 10 秒,内存不涨、帧率稳定;松手后几何位置正确 |
| 2D bbox | 各阀门 2D 符号方向、手柄位置与改前一致 |
@@ -618,6 +653,6 @@ Draw3dSolid(&dc, nColor); // :3062 拖拽时每帧都走到这里
1. ~~第一批 1~5(内存),单独一个 PR,可独立验证~~ —— 已完成,范围扩到 2.1~2.11 全部内存项,见 2.13
2. ~~第二批(`m_Combins` 存盘清空)~~ —— 已完成(PR #48),见 3.1、5.2
3. 第一批 6~11(逻辑),单独一个 PR
4. 确认 5.4 剩下的两个问题
3. ~~第一批 6~11(逻辑)~~ —— 已完成,范围扩到 3.2~3.14 共 10 项,见 5.1
4. 确认 5.4 剩下的问题(3.6 `HLR_CALC` 序列化、3.7 `GetInterface` 单位、3.12 `mat4` 双次变换)
5. 第三批按 4.7 度量结果排序,一项一个 PR
@@ -76,6 +76,9 @@ struct Point2d
namespace
{
//单个几何单元的参数块上限:正常只有几十字节到几十 KB,超出即视为图纸损坏
const Adesk::Int32 kMaxXPrimDataSize = 16 * 1024 * 1024;
bool isValidExtents(const AcDbExtents &ext)
{
// 检查是否仍然处于初始化的“反转”状态
@@ -188,10 +191,24 @@ namespace
pFiler->readInt32(&nSize);
pSolid->m_nSize = nSize;
//单个单元的参数块只有几十字节到几十 KB,超出上限说明图纸损坏。
//直接 new byte[巨量] 会当场失败,这里标记文件错误让整个 dwgInFields 退出
if (pSolid->m_nSize > kMaxXPrimDataSize)
{
pSolid->m_nSize = 0;
pFiler->setFilerStatus(Acad::eInvalidInput);
return pSolid;
}
if (pSolid->m_nSize > 0)
{
pSolid->m_pData = new byte[pSolid->m_nSize];
pFiler->readBytes(pSolid->m_pData, (Adesk::UInt32)pSolid->m_nSize);
if (pFiler->readBytes(pSolid->m_pData, (Adesk::UInt32)pSolid->m_nSize) != Acad::eOk)
{
delete[] (byte*)pSolid->m_pData;
pSolid->m_pData = NULL;
pSolid->m_nSize = 0;
}
}
//ID 存2个32为
@@ -825,8 +842,15 @@ namespace
it = v.FindMember(_T("__pData__"));
if (it == v.MemberEnd()) return false;
GEO2JSON::binary br(it->value);
p->m_pData = new PVOID[br._len];
::memcpy(p->m_pData, br._data, sizeof(PVOID) * br._len);
//br._len 是字节数,与 __nSize__ 同单位。原来按 PVOID 个数分配、
//再按 sizeof(PVOID)*len 拷贝,源缓冲区会越界读 7 倍。
//两者不一致说明 JSON 被改过,按实际数据长度为准
p->m_nSize = (int)br._len;
if (br._len > 0 && br._data != NULL)
{
p->m_pData = new byte[br._len];
::memcpy(p->m_pData, br._data, br._len);
}
it = v.FindMember(_T("__nActiveInf__"));
if (it == v.MemberEnd()) return false;
@@ -2110,7 +2134,11 @@ Acad::ErrorStatus AecDbEwEquipment::subErase(Adesk::Boolean erasing)
int nVal = 1;
m_mapSpecXData.GetAt(PIPE_VALVE, nVal);
if (eSubType == eValve || sName.Find(_T("阀") ) != -1 || sName.Find(_T("接头")))
//第三个条件原来写成 sName.Find(_T("接头")),Find 找不到返回 -1,
//非 0 即真,整个条件恒成立,删除任何设备都会走下面的合并管线逻辑
if (eSubType == eValve
|| sName.Find(_T("阀")) != -1
|| sName.Find(_T("接头")) != -1)
{
AecEqui::ValveType eVlvType = (AecEqui::ValveType)nVal;
//if (eVlvType >= AecEqui::eGateVlv && eVlvType <= AecEqui::eJYF)
@@ -2184,6 +2212,13 @@ Acad::ErrorStatus AecDbEwEquipment::copyFrom(const AcRxObject* pSrc)
m_nInfActive = pImp->m_nInfActive;
m_ptInsert = pImp->m_ptInsert;
m_vtDir = pImp->m_vtDir;
//这三个原来漏拷:m_bVert 决定阀门横放还是竖放(TransformEntity 要用),
//m_idDash / m_idCenter 是虚线与中心线所在的图层,丢了会跑到默认图层
m_bVert = pImp->m_bVert;
m_idDash = pImp->m_idDash;
m_idCenter = pImp->m_idCenter;
AecEqui::XPrimData* pSolid = NULL;
AecEqui::XPrimData* pSolid2 = NULL;
@@ -2646,7 +2681,7 @@ Acad::ErrorStatus AecDbEwEquipment::DrawHandFlg(AcGiDraw &dc) const
CString sFlag(_T(""));
if (nVt == 1) sFlag = _T("M");
else if (nVt == 2) sFlag = _T("G");
else sFlag == _T("Y");
else sFlag = _T("Y"); //液动;原来写成 ==,sFlag 一直是空串
AecInterface inf1, inf2;
GetInterface(0, inf1);
@@ -4220,14 +4255,21 @@ int AecDbEwEquipment::GetBodyBox(AcDbExtents& ext) const
Draw3d(dc);
}
AcDbExtents _ext;
//原来恒返回 TRUE,调用方无法区分"没取到几何"。
//_ext 也改为每轮新建,避免把上一轮的累积结果重复并入
BOOL bRet(FALSE);
for (size_t i = 0; i < ar3D.length(); i++)
{
AcDbEntity *p = (AcDbEntity*)ar3D[(int)i];
p->getGeomExtents(_ext);
if (p == NULL) continue;
AcDbExtents _ext;
if (p->getGeomExtents(_ext) != Acad::eOk) continue;
ext.addExt(_ext);
bRet = TRUE;
}
return TRUE;
return bRet;
}
AecDbObjXDataMap* AecDbEwEquipment::GetSpecialtyData(AcDb::OpenMode bMode)
@@ -12540,15 +12582,26 @@ void AecDbEwEquipment::setSectionID(const ESection &es, AcDbObjectId & id)
//取中心位置为做块的插入位置
DLLIMPEXP bool MakeBlockRefSet(AcDbVoidPtrArray& arEntPtrs,const AcDbObjectId& idLay,Adesk::Boolean bDoSubent,LPCTSTR pszName,AcDbDatabase* pDb = NULL)
{
AcDbExtents ext, extAdd;
AcDbExtents ext;
AcDbEntity *pEnt = NULL;
CString s;
for (int i = 0;i < arEntPtrs.length();i++)
{
pEnt = (AcDbEntity*)arEntPtrs.at(i);
if (!AcDbBlockReference::cast(pEnt))
pEnt->getGeomExtents(extAdd);
if (isValidExtents(ext))
if (pEnt == NULL) continue;
//块参照本身没有几何。原来不跳过,extAdd 会保留上一轮的值被重复累加
if (AcDbBlockReference::cast(pEnt))
continue;
//每轮重新取,避免 getGeomExtents 失败时沿用上一轮的范围
AcDbExtents extAdd;
if (pEnt->getGeomExtents(extAdd) != Acad::eOk)
continue;
//原来判断的是累加器 ext:第一轮它还是空状态,
//第一个实体的范围永远加不进去
if (isValidExtents(extAdd))
ext.addExt(extAdd);
}
@@ -12803,7 +12856,8 @@ int AecDbEwEquipment::GetSectionDraw(const AcGePoint3d& ptFr,const AcGePoint3d&
GetSectionDraw(plan,vtTo,arRegionPtrs);
int nCoundPre = arRegionPtrs.length();
return (nCount - nCoundPre);
//返回本次新增的个数。原来写成 nCount - nCoundPre(调用前减调用后),恒为 0 或负数
return (nCoundPre - nCount);
}
bool AecDbEwEquipment::GetPositionGrip(int nIndex, AecGripData *&pGrip) const
@@ -12915,6 +12969,11 @@ bool AecDbEwEquipment::ByJson(const SHJson::WValue &v)
void AecDbEwEquipment::clearGeoBuffer()
{
AssertWriteEnabled();
//调用方(改参数、改颜色之后)期望清掉缓存强制重建,
//原来函数体是空的,改完参数旧几何还在
Clean2DBuffer();
Clean3DBuffer();
}
void AecDbEwEquipment::TransformEntity(AcDbEntity *p
@@ -13179,13 +13238,14 @@ void AecDbEwEquipment::ProcessHlrResult(const AcArray<AcDbEntity *> &ar
_ar.removeAll();
// 把 preAr 中的 region 生成的结果挑出来,不显示
// 原来 erase(it) 之后仍然 ++it,迭代器已失效
std::map<AcDbEntity *, AcArray<AcDbEntity *>>::iterator it(_preAr.begin());
for (; it != _preAr.end(); ++it)
while (it != _preAr.end())
{
if (AcDbRegion::cast(it->first))
{
_preAr.erase(it);
}
_preAr.erase(it++); //先取下一个迭代器再删当前
else
++it;
}
}