重构碰撞检测结果的保存与加载逻辑,利用DocumentModels的CreatePathId、ResolvePathId等方法,解决保存和加载ModelItem路径的问题

This commit is contained in:
tian 2026-01-08 22:14:20 +08:00
parent ee1b0cbe32
commit 42481a5edc
5 changed files with 532 additions and 164 deletions

View File

@ -2735,8 +2735,421 @@ transform3dComponents.Translation = new Vector3D(origin_X, origin_Y, origin_Z);
Units units = Units.Meters;
models.SetModelUnitsAndTransform(model, units , newTransform3D, true);
### 如何唯一标识一个ModelItem
### 如何在运行时唯一标识一个ModelItem
使用 ModelItem.GetHashCode() 作为唯一标识符
使用 HashSet<ModelItem> 来存储和比较碰撞对象,这样可以正确使用 ModelItem.Equals() 和 GetHashCode() 方法,避免哈希冲突和跨运行时不一致的问题
## 13. 使用 DocumentModels API 持久化 ModelItem 引用
### 13.1 核心概念
**问题场景**:需要在不同会话之间保存和恢复 ModelItem 的引用(如碰撞检测结果、动画对象等)
**核心洞察**:使用 `DocumentModels.CreatePathId()``ResolvePathId()` 方法实现跨会话的 ModelItem 持久化
### 13.2 ModelItemPathId 结构
```csharp
// ✅ ModelItemPathId 包含两个关键信息
public class ModelItemPathId
{
public int ModelIndex { get; set; } // 模型索引NWD文件中的模型编号
public string PathId { get; set; } // 路径标识符(如 "0/682/0"
}
// 🔍 PathId 格式说明:
// - 使用斜杠分隔的层级路径
// - 例如:"0/682/0" 表示从根节点到目标节点的路径
// - 每个数字代表该层级的索引位置
```
### 13.3 保存 ModelItem 到数据库
```csharp
// ✅ 正确方法:使用 CreatePathId 获取持久化标识
public void SaveCollisionObject(ModelItem collidedObject, int resultId)
{
var document = Application.ActiveDocument;
// 获取 ModelItem 的持久化标识
var pathId = document.Models.CreatePathId(collidedObject);
// 保存到数据库
var record = new ClashDetectiveCollisionObjectRecord
{
ResultId = resultId,
ModelIndex = pathId.ModelIndex, // 保存模型索引
PathId = pathId.PathId, // 保存路径标识
DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collidedObject),
ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collidedObject)
};
pathDatabase.SaveClashDetectiveCollisionObject(record);
}
```
### 13.4 从数据库恢复 ModelItem
```csharp
// ✅ 正确方法:使用 ResolvePathId 恢复 ModelItem
public ModelItem LoadCollisionObject(int? modelIndex, string pathId)
{
try
{
var document = Application.ActiveDocument;
// 验证必需参数
if (!modelIndex.HasValue)
{
throw new InvalidOperationException("ModelIndex 为空,无法恢复 ModelItem");
}
if (string.IsNullOrEmpty(pathId))
{
throw new InvalidOperationException("PathId 为空,无法恢复 ModelItem");
}
// 构造 ModelItemPathId 对象
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId();
pathIdObj.ModelIndex = modelIndex.Value;
pathIdObj.PathId = pathId;
// 解析路径并恢复 ModelItem
ModelItem restoredItem = document.Models.ResolvePathId(pathIdObj);
if (restoredItem == null)
{
throw new InvalidOperationException($"无法通过 PathId 找到 ModelItem: ModelIndex={modelIndex}, PathId={pathId}");
}
// 验证恢复的对象是否有效
if (!ModelItemAnalysisHelper.IsModelItemValid(restoredItem))
{
throw new InvalidOperationException($"恢复的 ModelItem 无效: ModelIndex={modelIndex}, PathId={pathId}");
}
return restoredItem;
}
catch (Exception ex)
{
LogManager.Error($"恢复 ModelItem 失败: ModelIndex={modelIndex}, PathId={pathId}", ex);
throw;
}
}
```
### 13.5 完整示例:碰撞检测结果的保存和加载
```csharp
// ✅ 保存碰撞检测结果
public void SaveClashDetectiveResults(List<CollisionResult> collisions, ModelItem vehicle)
{
var document = Application.ActiveDocument;
// 1. 保存车辆对象信息
var vehiclePathId = document.Models.CreatePathId(vehicle);
var testRecord = new ClashDetectiveResultRecord
{
TestName = GenerateTestName(),
IsVirtualVehicle = false,
VehicleModelIndex = vehiclePathId.ModelIndex,
VehiclePathId = vehiclePathId.PathId,
// ... 其他字段
};
int resultId = pathDatabase.SaveClashDetectiveResult(testRecord);
// 2. 保存所有被撞对象
var collisionObjects = new List<ClashDetectiveCollisionObjectRecord>();
foreach (var collision in collisions)
{
if (collision.Item2 != null) // 只保存被撞对象Item2
{
var objectPathId = document.Models.CreatePathId(collision.Item2);
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
{
ResultId = resultId,
ModelIndex = objectPathId.ModelIndex,
PathId = objectPathId.PathId,
DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2),
ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2)
});
}
}
pathDatabase.SaveClashDetectiveCollisionObjects(resultId, collisionObjects);
}
// ✅ 加载碰撞检测结果
public List<CollisionResult> LoadClashDetectiveResults(string testName)
{
var document = Application.ActiveDocument;
// 1. 从数据库读取测试信息
var testInfo = pathDatabase.GetClashDetectiveResult(testName);
if (testInfo == null)
{
throw new InvalidOperationException($"未找到测试记录: {testName}");
}
// 2. 恢复车辆对象
ModelItem vehicle;
if (testInfo.IsVirtualVehicle)
{
// 虚拟车辆:创建新的虚拟车辆对象
vehicle = VirtualVehicleManager.Instance.CreateVirtualVehicle(
testInfo.VirtualVehicleLength,
testInfo.VirtualVehicleWidth,
testInfo.VirtualVehicleHeight
);
}
else
{
// 真实车辆:使用 ResolvePathId 恢复
vehicle = LoadCollisionObject(testInfo.VehicleModelIndex, testInfo.VehiclePathId);
}
// 3. 恢复所有被撞对象并重建碰撞结果
var collisionObjects = pathDatabase.GetClashDetectiveCollisionObjects(testInfo.Id);
var results = new List<CollisionResult>();
foreach (var obj in collisionObjects)
{
ModelItem collidedObject = LoadCollisionObject(obj.ModelIndex, obj.PathId);
var collisionResult = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
DisplayName = $"历史碰撞: {obj.DisplayName}",
Status = ClashResultStatus.Active,
Item1 = vehicle,
Item2 = collidedObject,
Center = collidedObject.BoundingBox().Center,
Distance = 0.0,
CreatedTime = DateTime.Now,
OriginalItem1 = vehicle,
OriginalItem2 = collidedObject,
HasContainerMapping = true
};
results.Add(collisionResult);
}
return results;
}
```
### 13.6 数据库表设计建议
```sql
-- ✅ 推荐的数据库表结构(最小化冗余)
CREATE TABLE ClashDetectiveCollisionObjects (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ResultId INTEGER NOT NULL,
ModelIndex INTEGER, -- 模型索引(可为空)
PathId TEXT, -- 路径标识符(可为空)
DisplayName TEXT, -- 显示名称用于UI显示
ObjectName TEXT, -- 对象名称(用于日志)
FOREIGN KEY(ResultId) REFERENCES ClashDetectiveResults(Id)
);
-- ❌ 避免冗余字段
-- ModelItemPath TEXT, -- 与 PathId 重复,不需要
```
**关键要点**
- **最小化存储**:只需要 `ModelIndex``PathId` 两个字段即可恢复对象
- **避免冗余**:不要存储与 PathId 相同的 ModelItemPath 字段
- **保留显示信息**DisplayName 和 ObjectName 用于 UI 显示和日志,不影响对象恢复
### 13.7 常见问题和解决方案
#### 问题1PathId 类型混淆
```csharp
// ❌ 错误:以为 PathId 是 int 类型
public class ClashDetectiveCollisionObjectRecord
{
public int? PathId { get; set; } // 错误PathId 是 string不是 int
}
// ✅ 正确PathId 是 string 类型
public class ClashDetectiveCollisionObjectRecord
{
public string PathId { get; set; } // 正确:格式如 "0/682/0"
}
```
#### 问题2参数验证不充分
```csharp
// ❌ 错误:没有验证参数
public ModelItem LoadObject(int? modelIndex, string pathId)
{
var pathIdObj = new ModelItemPathId();
pathIdObj.ModelIndex = modelIndex.Value; // 可能为空
pathIdObj.PathId = pathId; // 可能为空
return document.Models.ResolvePathId(pathIdObj);
}
// ✅ 正确:验证参数并抛出明确异常
public ModelItem LoadObject(int? modelIndex, string pathId)
{
if (!modelIndex.HasValue)
{
throw new InvalidOperationException("ModelIndex 为空,无法恢复 ModelItem");
}
if (string.IsNullOrEmpty(pathId))
{
throw new InvalidOperationException("PathId 为空,无法恢复 ModelItem");
}
var pathIdObj = new ModelItemPathId();
pathIdObj.ModelIndex = modelIndex.Value;
pathIdObj.PathId = pathId;
var restoredItem = document.Models.ResolvePathId(pathIdObj);
if (restoredItem == null)
{
throw new InvalidOperationException($"无法通过 PathId 找到 ModelItem: ModelIndex={modelIndex}, PathId={pathId}");
}
return restoredItem;
}
```
#### 问题3向后兼容代码掩盖问题
```csharp
// ❌ 错误:使用向后兼容逻辑掩盖问题
public ModelItem LoadObject(int? modelIndex, string pathId, string oldPath)
{
ModelItem item = null;
// 尝试新方法
if (modelIndex.HasValue && !string.IsNullOrEmpty(pathId))
{
try
{
item = document.Models.ResolvePathId(new ModelItemPathId { ModelIndex = modelIndex.Value, PathId = pathId });
}
catch { } // 静默失败
}
// 回退到旧方法
if (item == null && !string.IsNullOrEmpty(oldPath))
{
item = FindModelItemByOldPath(oldPath); // 旧方法
}
return item; // 可能返回 null掩盖了问题
}
// ✅ 正确:失败直接抛出异常,不掩盖问题
public ModelItem LoadObject(int? modelIndex, string pathId)
{
if (!modelIndex.HasValue || string.IsNullOrEmpty(pathId))
{
throw new InvalidOperationException($"参数无效: ModelIndex={modelIndex}, PathId={pathId}");
}
try
{
var pathIdObj = new ModelItemPathId { ModelIndex = modelIndex.Value, PathId = pathId };
var item = document.Models.ResolvePathId(pathIdObj);
if (item == null)
{
throw new InvalidOperationException($"无法找到 ModelItem: ModelIndex={modelIndex}, PathId={pathId}");
}
return item;
}
catch (Exception ex)
{
throw new InvalidOperationException($"恢复 ModelItem 失败: ModelIndex={modelIndex}, PathId={pathId}", ex);
}
}
```
### 13.8 最佳实践总结
| 最佳实践 | 说明 | 示例 |
|---------|------|------|
| **使用 CreatePathId** | 获取 ModelItem 的持久化标识 | `var pathId = document.Models.CreatePathId(item)` |
| **保存 ModelIndex 和 PathId** | 两个字段都是必需的 | `ModelIndex = pathId.ModelIndex, PathId = pathId.PathId` |
| **验证参数** | 加载前验证参数有效性 | `if (!modelIndex.HasValue) throw ...` |
| **失败抛出异常** | 不掩盖问题,直接抛出异常 | `if (item == null) throw ...` |
| **避免冗余字段** | 不存储与 PathId 重复的数据 | 删除 ModelItemPath 字段 |
| **保留显示信息** | DisplayName 用于 UI 显示 | `DisplayName = item.DisplayName` |
| **线程安全** | 在主线程中执行 API 调用 | 使用 `Dispatcher.Invoke` 包装 |
| **错误处理** | 提供详细的错误信息 | 包含 ModelIndex 和 PathId 在异常消息中 |
### 13.9 性能和可靠性考虑
1. **PathId 的稳定性**
- 只要 NWD 文件结构不变PathId 就会保持稳定
- 如果 NWD 文件被重新导出或结构改变PathId 可能失效
2. **批量恢复优化**
```csharp
// ✅ 批量恢复时,先收集所有 PathId
var pathIds = collisionObjects.Select(obj => new ModelItemPathId
{
ModelIndex = obj.ModelIndex.Value,
PathId = obj.PathId
}).ToList();
// 然后一次性恢复(如果 API 支持)
// 或者逐个恢复并收集失败的对象
var failedItems = new List<string>();
foreach (var pathId in pathIds)
{
try
{
var item = document.Models.ResolvePathId(pathId);
// 处理恢复的对象
}
catch (Exception ex)
{
failedItems.Add($"{pathId.ModelIndex}:{pathId.PathId}");
}
}
```
3. **缓存机制**
```csharp
// ✅ 使用缓存减少重复查询
private Dictionary<string, ModelItem> _modelItemCache = new Dictionary<string, ModelItem>();
public ModelItem GetOrLoadModelItem(int? modelIndex, string pathId)
{
string cacheKey = $"{modelIndex}:{pathId}";
if (_modelItemCache.TryGetValue(cacheKey, out ModelItem cachedItem))
{
return cachedItem;
}
var item = LoadCollisionObject(modelIndex, pathId);
_modelItemCache[cacheKey] = item;
return item;
}
```
### 13.10 与其他持久化方法的对比
| 方法 | 优点 | 缺点 | 适用场景 |
|------|------|------|---------|
| **CreatePathId/ResolvePathId** | 官方API稳定可靠跨会话有效 | 需要保存两个字段 | 推荐用于所有持久化场景 |
| **ModelItem.GetHashCode()** | 简单,单个值 | 跨会话无效,哈希冲突 | 仅用于运行时去重 |
| **索引路径(旧方法)** | 简单易懂 | 不稳定,易失效 | 不推荐使用 |
| **DisplayName** | 易于理解 | 不唯一,可能重复 | 仅用于UI显示 |
**结论**`CreatePathId/ResolvePathId` 是 Navisworks API 提供的官方持久化方法,应该作为首选方案。

View File

@ -7,7 +7,7 @@
1. [ ] BUG虚拟车辆模型每次点击都重建
2. [x] BUG碰撞结果高亮应该显示clashdetective检测的结果
3. [x] 优化对clashdetective的检测结果进行向上合并找到集合对象。
4. [ ] (功能)碰撞检测结果保存数据库,列表展示
4. [x] (功能)碰撞检测结果保存数据库,列表展示
### [2025/12/25]

View File

@ -175,31 +175,24 @@ namespace NavisworksTransport
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
if (pathDatabase != null)
{
// 获取动画对象名称
// 获取动画对象名称和路径信息
string animatedObjectName = "未知对象";
int? vehicleModelIndex = null;
string vehiclePathId = null;
if (!isVirtualVehicle && animatedObject != null)
{
animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject);
// 获取真实车辆的 PathId 信息
var pathId = Application.ActiveDocument.Models.CreatePathId(animatedObject);
vehicleModelIndex = pathId.ModelIndex;
vehiclePathId = pathId.PathId;
}
else if (isVirtualVehicle)
{
animatedObjectName = "虚拟车辆";
}
// 设置车辆路径信息
string vehicleModelItemPath = null;
if (!isVirtualVehicle && animatedObject != null)
{
// 只有真实车辆才记录路径
var paths = ModelItemAnalysisHelper.GetModelItemIndexPaths(animatedObject);
if (paths.Count > 0)
{
vehicleModelItemPath = string.Join(";", paths);
}
}
// 打印虚拟车辆尺寸(用于调试)
LogManager.Info($"[SaveClashDetectiveResultToDatabase] IsVirtualVehicle={isVirtualVehicle}, 虚拟车辆尺寸: Length={virtualVehicleLength:F2}m, Width={virtualVehicleWidth:F2}m, Height={virtualVehicleHeight:F2}m");
@ -216,7 +209,8 @@ namespace NavisworksTransport
DetectionGap = detectionGap,
AnimatedObjectName = animatedObjectName,
IsVirtualVehicle = isVirtualVehicle,
VehicleModelItemPath = vehicleModelItemPath,
VehicleModelIndex = vehicleModelIndex,
VehiclePathId = vehiclePathId,
VirtualVehicleLength = virtualVehicleLength,
VirtualVehicleWidth = virtualVehicleWidth,
VirtualVehicleHeight = virtualVehicleHeight,
@ -233,17 +227,17 @@ namespace NavisworksTransport
// 只保存被撞到的物体Item2
if (collision.Item2 != null)
{
var paths = ModelItemAnalysisHelper.GetModelItemIndexPaths(collision.Item2);
foreach (var path in paths)
// 使用 CreatePathId API 获取 ModelIndex 和 PathId
var pathId = Application.ActiveDocument.Models.CreatePathId(collision.Item2);
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
{
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
{
ResultId = resultId,
ModelItemPath = path,
DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2),
ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2)
});
}
ResultId = resultId,
ModelIndex = pathId.ModelIndex,
PathId = pathId.PathId,
DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2),
ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2)
});
}
}
@ -282,7 +276,7 @@ namespace NavisworksTransport
// 1. 从数据库读取测试信息
var testInfoSql = @"
SELECT Id, PathName, RouteId, IsVirtualVehicle, VehicleModelItemPath,
SELECT Id, PathName, RouteId, IsVirtualVehicle, VehicleModelIndex, VehiclePathId,
VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight
FROM ClashDetectiveResults
WHERE TestName = @testName
@ -302,7 +296,8 @@ namespace NavisworksTransport
PathName = reader["PathName"].ToString(),
RouteId = reader["RouteId"]?.ToString(),
IsVirtualVehicle = Convert.ToBoolean(reader["IsVirtualVehicle"]),
VehicleModelItemPath = reader["VehicleModelItemPath"]?.ToString(),
VehicleModelIndex = reader["VehicleModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["VehicleModelIndex"]) : (int?)null,
VehiclePathId = reader["VehiclePathId"] != DBNull.Value ? reader["VehiclePathId"].ToString() : null,
VirtualVehicleLength = reader["VirtualVehicleLength"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleLength"]) : 0.0,
VirtualVehicleWidth = reader["VirtualVehicleWidth"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleWidth"]) : 0.0,
VirtualVehicleHeight = reader["VirtualVehicleHeight"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleHeight"]) : 0.0
@ -326,36 +321,36 @@ namespace NavisworksTransport
testInfo.VirtualVehicleLength,
testInfo.VirtualVehicleWidth,
testInfo.VirtualVehicleHeight
);
if (vehicleObject == null)
{
LogManager.Error($"[LoadClashDetectiveResultsFromDatabase] 创建虚拟车辆失败");
return null;
}
) ?? throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 创建虚拟车辆失败");
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已创建虚拟车辆: {testInfo.VirtualVehicleLength}×{testInfo.VirtualVehicleWidth}×{testInfo.VirtualVehicleHeight}m");
}
else if (!string.IsNullOrEmpty(testInfo.VehicleModelItemPath))
else if (testInfo.VehicleModelIndex.HasValue && !string.IsNullOrEmpty(testInfo.VehiclePathId))
{
// 通过路径查找真实车辆
var paths = testInfo.VehicleModelItemPath.Split(';');
vehicleObject = NavisworksApiHelper.FindModelItemByPath(paths[0]);
if (vehicleObject == null)
// 通过 PathId 查找真实车辆
try
{
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 无法通过路径找到车辆对象: {paths[0]}");
return null;
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
{
ModelIndex = testInfo.VehicleModelIndex.Value,
PathId = testInfo.VehiclePathId
};
vehicleObject = Application.ActiveDocument.Models.ResolvePathId(pathIdObj) ?? throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法通过 PathId 找到车辆对象: ModelIndex={testInfo.VehicleModelIndex}, PathId={testInfo.VehiclePathId}");
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已找到真实车辆: {ModelItemAnalysisHelper.GetSafeDisplayName(vehicleObject)}");
}
catch (Exception ex)
{
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] ResolvePathId 失败: ModelIndex={testInfo.VehicleModelIndex}, PathId={testInfo.VehiclePathId}", ex);
}
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已找到真实车辆: {ModelItemAnalysisHelper.GetSafeDisplayName(vehicleObject)}");
}
if (vehicleObject == null)
{
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 无法重建车辆对象");
return null;
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法重建车辆对象: IsVirtualVehicle={testInfo.IsVirtualVehicle}, VehicleModelIndex={testInfo.VehicleModelIndex}, VehiclePathId={testInfo.VehiclePathId}");
}
// 3. 从数据库读取被撞物体信息
var collisionObjectsSql = @"
SELECT ModelItemPath, DisplayName, ObjectName
SELECT ModelIndex, PathId, DisplayName, ObjectName
FROM ClashDetectiveCollisionObjects
WHERE ResultId = @resultId
";
@ -370,7 +365,8 @@ namespace NavisworksTransport
{
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
{
ModelItemPath = reader["ModelItemPath"]?.ToString(),
ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null,
PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null,
DisplayName = reader["DisplayName"]?.ToString(),
ObjectName = reader["ObjectName"]?.ToString()
});
@ -380,41 +376,53 @@ namespace NavisworksTransport
// 4. 重建碰撞结果
var results = new List<CollisionResult>();
var processedPaths = new HashSet<string>();
foreach (var obj in collisionObjects)
{
// 通过路径查找被撞物体
var collidedObject = NavisworksApiHelper.FindModelItemByPath(obj.ModelItemPath);
if (collidedObject != null && ModelItemAnalysisHelper.IsModelItemValid(collidedObject))
ModelItem collidedObject = null;
// 优先使用 ResolvePathId API新方式
if (obj.ModelIndex.HasValue && !string.IsNullOrEmpty(obj.PathId))
{
// 避免重复添加(同一物体可能被撞多次)
if (!processedPaths.Contains(obj.ModelItemPath))
try
{
processedPaths.Add(obj.ModelItemPath);
var collisionResult = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
DisplayName = $"历史碰撞: {obj.DisplayName}",
Status = ClashResultStatus.Active,
Item1 = vehicleObject,
Item2 = collidedObject,
Center = collidedObject.BoundingBox().Center,
Distance = 0.0,
CreatedTime = DateTime.Now,
OriginalItem1 = vehicleObject,
OriginalItem2 = collidedObject,
HasContainerMapping = true
};
results.Add(collisionResult);
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId();
pathIdObj.ModelIndex = obj.ModelIndex.Value;
pathIdObj.PathId = obj.PathId;
collidedObject = Application.ActiveDocument.Models.ResolvePathId(pathIdObj);
}
catch (Exception ex)
{
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] ResolvePathId 失败: ModelIndex={obj.ModelIndex}, PathId={obj.PathId}", ex);
}
}
else
if (collidedObject == null)
{
LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 无法通过路径找到被撞物体: {obj.ModelItemPath}");
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法通过路径找到车辆对象: {obj.ModelIndex},{obj.PathId}");
}
if (!ModelItemAnalysisHelper.IsModelItemValid(collidedObject))
{
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 找到的 ModelItem 无效: {obj.ModelIndex},{obj.PathId}");
}
var collisionResult = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
DisplayName = $"历史碰撞: {obj.DisplayName}",
Status = ClashResultStatus.Active,
Item1 = vehicleObject,
Item2 = collidedObject,
Center = collidedObject.BoundingBox().Center,
Distance = 0.0,
CreatedTime = DateTime.Now,
OriginalItem1 = vehicleObject,
OriginalItem2 = collidedObject,
HasContainerMapping = true
};
results.Add(collisionResult);
}
LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 从数据库加载测试 '{testName}' 完成,重建了 {results.Count} 个碰撞结果");

View File

@ -176,7 +176,8 @@ namespace NavisworksTransport
DetectionGap REAL,
AnimatedObjectName TEXT,
IsVirtualVehicle INTEGER,
VehicleModelItemPath TEXT,
VehicleModelIndex INTEGER,
VehiclePathId TEXT,
VirtualVehicleLength REAL,
VirtualVehicleWidth REAL,
VirtualVehicleHeight REAL,
@ -189,7 +190,8 @@ namespace NavisworksTransport
CREATE TABLE IF NOT EXISTS ClashDetectiveCollisionObjects (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ResultId INTEGER NOT NULL,
ModelItemPath TEXT NOT NULL,
ModelIndex INTEGER,
PathId TEXT,
DisplayName TEXT,
ObjectName TEXT,
FOREIGN KEY(ResultId) REFERENCES ClashDetectiveResults(Id) ON DELETE CASCADE
@ -203,7 +205,7 @@ namespace NavisworksTransport
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_path_name ON ClashDetectiveResults(PathName)");
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_test_time ON ClashDetectiveResults(TestTime DESC)");
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_objects_result ON ClashDetectiveCollisionObjects(ResultId)");
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_objects_path ON ClashDetectiveCollisionObjects(ModelItemPath)");
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_objects_path ON ClashDetectiveCollisionObjects(PathId)");
}
/// <summary>
@ -414,10 +416,10 @@ namespace NavisworksTransport
var sql = @"
INSERT INTO ClashDetectiveResults
(TestName, PathName, RouteId, TestTime, CollisionCount, AnimationCollisionCount,
FrameRate, Duration, DetectionGap, AnimatedObjectName, IsVirtualVehicle, VehicleModelItemPath,
FrameRate, Duration, DetectionGap, AnimatedObjectName, IsVirtualVehicle, VehicleModelIndex, VehiclePathId,
VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight, CreatedAt)
VALUES (@testName, @pathName, @routeId, @testTime, @collisionCount, @animationCollisionCount,
@frameRate, @duration, @detectionGap, @animatedObjectName, @isVirtualVehicle, @vehiclePath,
@frameRate, @duration, @detectionGap, @animatedObjectName, @isVirtualVehicle, @vehicleModelIndex, @vehiclePathId,
@virtualVehicleLength, @virtualVehicleWidth, @virtualVehicleHeight, @createdAt)
";
@ -435,7 +437,8 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@detectionGap", record.DetectionGap);
cmd.Parameters.AddWithValue("@animatedObjectName", record.AnimatedObjectName ?? "");
cmd.Parameters.AddWithValue("@isVirtualVehicle", record.IsVirtualVehicle);
cmd.Parameters.AddWithValue("@vehiclePath", record.VehicleModelItemPath ?? "");
cmd.Parameters.AddWithValue("@vehicleModelIndex", record.VehicleModelIndex.HasValue ? (object)record.VehicleModelIndex.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@vehiclePathId", record.VehiclePathId ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@virtualVehicleLength", record.VirtualVehicleLength);
cmd.Parameters.AddWithValue("@virtualVehicleWidth", record.VirtualVehicleWidth);
cmd.Parameters.AddWithValue("@virtualVehicleHeight", record.VirtualVehicleHeight);
@ -589,8 +592,8 @@ namespace NavisworksTransport
{
var sql = @"
INSERT INTO ClashDetectiveCollisionObjects
(ResultId, ModelItemPath, DisplayName, ObjectName)
VALUES (@resultId, @path, @displayName, @objectName)
(ResultId, ModelIndex, PathId, DisplayName, ObjectName)
VALUES (@resultId, @modelIndex, @pathId, @displayName, @objectName)
";
foreach (var obj in objects)
@ -598,7 +601,8 @@ namespace NavisworksTransport
using (var cmd = new SQLiteCommand(sql, _connection))
{
cmd.Parameters.AddWithValue("@resultId", resultId);
cmd.Parameters.AddWithValue("@path", obj.ModelItemPath ?? "");
cmd.Parameters.AddWithValue("@modelIndex", obj.ModelIndex.HasValue ? (object)obj.ModelIndex.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@pathId", obj.PathId ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@displayName", obj.DisplayName ?? "");
cmd.Parameters.AddWithValue("@objectName", obj.ObjectName ?? "");
cmd.ExecuteNonQuery();
@ -626,7 +630,7 @@ namespace NavisworksTransport
try
{
var sql = @"
SELECT Id, ResultId, ModelItemPath, DisplayName, ObjectName
SELECT Id, ResultId, ModelIndex, PathId, DisplayName, ObjectName
FROM ClashDetectiveCollisionObjects
WHERE ResultId = @resultId
";
@ -642,7 +646,8 @@ namespace NavisworksTransport
{
Id = Convert.ToInt32(reader["Id"]),
ResultId = Convert.ToInt32(reader["ResultId"]),
ModelItemPath = reader["ModelItemPath"]?.ToString(),
ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null,
PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null,
DisplayName = reader["DisplayName"]?.ToString(),
ObjectName = reader["ObjectName"]?.ToString()
});
@ -660,29 +665,6 @@ namespace NavisworksTransport
return results;
}
/// <summary>
/// 根据ModelItemPath查询碰撞次数
/// </summary>
public int GetCollisionCountByModelItemPath(string modelItemPath)
{
try
{
var sql = "SELECT COUNT(*) FROM ClashDetectiveCollisionObjects WHERE ModelItemPath = @path";
using (var cmd = new SQLiteCommand(sql, _connection))
{
cmd.Parameters.AddWithValue("@path", modelItemPath);
var result = cmd.ExecuteScalar();
return Convert.ToInt32(result);
}
}
catch (Exception ex)
{
LogManager.Error($"查询碰撞次数失败: {ex.Message}", ex);
return 0;
}
}
#endregion
/// <summary>
@ -1210,7 +1192,8 @@ namespace NavisworksTransport
public double DetectionGap { get; set; }
public string AnimatedObjectName { get; set; }
public bool IsVirtualVehicle { get; set; }
public string VehicleModelItemPath { get; set; }
public int? VehicleModelIndex { get; set; }
public string VehiclePathId { get; set; }
public double VirtualVehicleLength { get; set; }
public double VirtualVehicleWidth { get; set; }
public double VirtualVehicleHeight { get; set; }
@ -1224,7 +1207,8 @@ namespace NavisworksTransport
{
public int Id { get; set; }
public int ResultId { get; set; }
public string ModelItemPath { get; set; }
public int? ModelIndex { get; set; }
public string PathId { get; set; }
public string DisplayName { get; set; }
public string ObjectName { get; set; }
}

View File

@ -132,74 +132,37 @@ namespace NavisworksTransport.Utils
}
}
/// <summary>
/// 通过索引路径查找ModelItem
/// 路径格式:逗号分隔的索引数组,如"1,3,5"
/// 注意COM API的路径是1-based索引需要转换为0-based
/// </summary>
/// <param name="pathString">路径字符串</param>
/// <returns>找到的ModelItem如果找不到返回null</returns>
public static ModelItem FindModelItemByPath(string pathString)
{
if (string.IsNullOrEmpty(pathString))
return null;
/// <summary>
/// 使用 ModelItemPathId 查找 ModelItem推荐方式
/// </summary>
/// <param name="modelIndex">模型索引</param>
/// <param name="pathId">路径标识符</param>
/// <returns>找到的ModelItem如果找不到返回null</returns>
public static ModelItem FindModelItemByPathId(int modelIndex, string pathId)
{
try
{
// 解析路径数组1-based索引
var pathParts = pathString.Split(',')
.Select(p => p.Trim())
.Where(p => !string.IsNullOrEmpty(p))
.Select(p => int.Parse(p))
.ToArray();
if (pathParts.Length == 0)
{
LogManager.Warning($"[FindModelItemByPath] 路径 '{pathString}' 解析失败");
return null;
}
// 从RootItems开始查找
var rootItems = Application.ActiveDocument.Models.RootItems;
var firstRoot = rootItems.FirstOrDefault();
var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId();
pathIdObj.ModelIndex = modelIndex;
pathIdObj.PathId = pathId;
var modelItem = Application.ActiveDocument.Models.ResolvePathId(pathIdObj);
if (firstRoot == null)
if (modelItem != null)
{
LogManager.Warning("[FindModelItemByPath] 文档根节点为空");
return null;
}
// 递归查找
var currentItem = firstRoot;
for (int i = 0; i < pathParts.Length; i++)
{
// COM API是1-based索引转换为0-based
var targetIndex = pathParts[i] - 1;
var children = currentItem.Children;
if (children == null || children.Count() <= targetIndex)
{
LogManager.Warning($"[FindModelItemByPath] 索引超出范围: 索引={targetIndex}, 子节点数={children?.Count() ?? 0}");
return null;
}
currentItem = children.ElementAt(targetIndex);
}
if (currentItem != null)
{
LogManager.Debug($"[FindModelItemByPath] 成功找到ModelItem: {currentItem.DisplayName}");
LogManager.Debug($"[FindModelItemByPathId] 成功找到ModelItem: ModelIndex={modelIndex}, PathId={pathId}");
}
else
{
LogManager.Warning($"[FindModelItemByPath] 未找到ModelItem: {pathString}");
LogManager.Warning($"[FindModelItemByPathId] 未找到ModelItem: ModelIndex={modelIndex}, PathId={pathId}");
}
return currentItem;
return modelItem;
}
catch (Exception ex)
{
LogManager.Error($"[FindModelItemByPath] 查找失败: {ex.Message}", ex);
LogManager.Error($"[FindModelItemByPathId] 查找失败: {ex.Message}", ex);
return null;
}
}