重构碰撞检测结果的保存与加载逻辑,利用DocumentModels的CreatePathId、ResolvePathId等方法,解决保存和加载ModelItem路径的问题
This commit is contained in:
parent
ee1b0cbe32
commit
42481a5edc
@ -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 常见问题和解决方案
|
||||
|
||||
#### 问题1:PathId 类型混淆
|
||||
|
||||
```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 提供的官方持久化方法,应该作为首选方案。
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
1. [ ] (BUG)虚拟车辆模型每次点击都重建
|
||||
2. [x] (BUG)碰撞结果高亮,应该显示clashdetective检测的结果
|
||||
3. [x] (优化)对clashdetective的检测结果,进行向上合并,找到集合对象。
|
||||
4. [ ] (功能)碰撞检测结果保存数据库,列表展示
|
||||
4. [x] (功能)碰撞检测结果保存数据库,列表展示
|
||||
|
||||
### [2025/12/25]
|
||||
|
||||
|
||||
@ -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} 个碰撞结果");
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user