diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj
index 53068b9..7e31d43 100644
--- a/NavisworksTransportPlugin.csproj
+++ b/NavisworksTransportPlugin.csproj
@@ -170,6 +170,10 @@
+
+
+
+
@@ -268,7 +272,6 @@
-
diff --git a/doc/design/2026/geometry3Sharp空间数据结构集成方案.md b/doc/design/2026/geometry3Sharp空间数据结构集成方案.md
index 723489e..0ea0fc5 100644
--- a/doc/design/2026/geometry3Sharp空间数据结构集成方案.md
+++ b/doc/design/2026/geometry3Sharp空间数据结构集成方案.md
@@ -141,7 +141,7 @@ double WindingNumber(Point3d point);
#### 应用场景
-**场景 1:优化 GetPotentialColliders()**
+场景 1:优化 GetPotentialColliders()**
当前实现(线性遍历):
@@ -176,7 +176,7 @@ var potentialColliders = sceneTree.FindNearestTriangles(
- 优化后:O(log n) ≈ log₂(290) ≈ 8 次树节点遍历
- **加速比**:约 36 倍
-**场景 2:精确碰撞检测**
+场景 2:精确碰撞检测**
替代简单包围盒检测,使用三角形级别的精确相交:
@@ -205,7 +205,7 @@ tree.Build(BuildStrategy.TopDownMidpoint);
#### 技术挑战
-**挑战 1:Navisworks Geometry → DMesh3 转换**
+挑战 1:Navisworks Geometry → DMesh3 转换
Navisworks 的 `ModelItem` 不直接暴露三角网格,需要通过几何提取:
@@ -266,7 +266,7 @@ List FindPointsInBox(AxisAlignedBox3d box);
#### 应用场景
-**场景 1:加速动画碰撞检测**
+场景 1:加速动画碰撞检测
当前问题:
@@ -312,7 +312,7 @@ foreach (var obstacle in nearbyObstacles)
- 优化后:每帧检测 5-15 个对象(仅邻近格子)
- **加速比**:约 7-20 倍
-**场景 2:动态场景更新**
+场景 2:动态场景更新
支持障碍物动态添加/移动:
@@ -377,7 +377,7 @@ DistTriangle3Triangle3 // 三角形到三角形的距离
#### 应用场景
-**场景 1:精确碰撞容差**
+场景 1:精确碰撞容差
当前问题:
@@ -412,7 +412,7 @@ foreach (var triangle1 in animatedObjectMesh.Triangles)
}
```
-**场景 2:碰撞报告增强**
+场景 2:碰撞报告增强
在碰撞报告中显示:
@@ -454,7 +454,7 @@ IntrRay3AxisAlignedBox3 // 射线-轴对齐盒相交
#### 应用场景
-**场景 1:路径可达性验证**
+场景 1:路径可达性验证
检查两点间的直线路径是否被障碍物阻挡:
@@ -487,7 +487,7 @@ public bool IsPathClear(Point3D start, Point3D end, IEnumerable obstacle
}
```
-**场景 2:视线检测**
+场景 2:视线检测
判断两个物体之间是否有遮挡:
@@ -567,7 +567,7 @@ for (int i = 0; i < path.Count - 1; i++)
### 5.1 整体架构
-```
+```graph
┌─────────────────────────────────────────────────────────────┐
│ Navisworks API │
│ (ModelItem, BoundingBox3D, Geometry) │
@@ -1057,7 +1057,7 @@ private List GetPotentialColliders()
### 9.3 集成策略
-```
+```graph
geometry3Sharp 基础库
├── 体素网格模块 (用于路径规划)
│ ├── MeshSignedDistanceGrid
diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs
index b09a5ab..6806c9b 100644
--- a/src/Core/Animation/PathAnimationManager.cs
+++ b/src/Core/Animation/PathAnimationManager.cs
@@ -6,6 +6,8 @@ using System.Windows.Forms;
using System.Windows.Threading;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
+using NavisworksTransport.Core.Config;
+using NavisworksTransport.Core.Spatial;
using NavisworksTransport.Utils;
using NavisApplication = Autodesk.Navisworks.Api.Application;
@@ -376,17 +378,36 @@ namespace NavisworksTransport.Core.Animation
originalBoundingBox.Max.Y - originalBoundingBox.Min.Y,
originalBoundingBox.Max.Z - originalBoundingBox.Min.Z
);
-
- // 获取场景中所有可能碰撞的对象
- var potentialColliders = GetPotentialColliders();
- LogManager.Info($"潜在碰撞对象数: {potentialColliders.Count}");
-
+
+ // 【新架构】构建全局空间索引(单层架构)
+ LogManager.Info("=== 构建全局空间索引 ===");
+ var spatialIndexManager = SpatialIndexManager.Instance;
+
+ // 使用系统配置中的车辆宽度作为格子大小(确保合理的空间分割粒度)
+ double vehicleWidthMeters = ConfigManager.Instance.Current.PathEditing.VehicleWidthMeters;
+ double cellSizeInModelUnits = UnitsConverter.ConvertFromMeters(vehicleWidthMeters);
+
+ LogManager.Info($"[空间索引] 车辆宽度: {vehicleWidthMeters:F2}米 → 格子大小: {cellSizeInModelUnits:F2}模型单位");
+
+ spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits);
+ LogManager.Info(spatialIndexManager.GetStatistics());
+
+ // 计算搜索半径:动画对象包围盒的对角线 + 检测间隙
+ double objectDiagonal = Math.Sqrt(
+ boundingBoxSize.X * boundingBoxSize.X +
+ boundingBoxSize.Y * boundingBoxSize.Y +
+ boundingBoxSize.Z * boundingBoxSize.Z
+ );
+ double searchRadiusInModelUnits = objectDiagonal / 2 + _detectionGap;
+
+ LogManager.Info($"空间查询半径: {searchRadiusInModelUnits:F2} 模型单位");
+
// 预计算每一帧
for (int i = 0; i < totalFrames; i++)
{
double progress = (double)i / totalFrames;
var framePosition = InterpolatePosition(progress);
-
+
// 创建帧数据
var frame = new AnimationFrame
{
@@ -395,12 +416,19 @@ namespace NavisworksTransport.Core.Animation
Position = framePosition,
Collisions = new List()
};
-
+
// 虚拟碰撞检测(不移动实际物体)
var virtualBoundingBox = CreateVirtualBoundingBox(framePosition, boundingBoxSize);
-
- // 检测与所有潜在对象的碰撞
- foreach (var collider in potentialColliders)
+
+ // 【新架构】使用空间索引查询附近对象
+ var nearbyObjects = spatialIndexManager.FindNearbyObjects(
+ framePosition,
+ searchRadiusInModelUnits,
+ excludeObject: _animatedObject
+ );
+
+ // 检测与附近对象的碰撞
+ foreach (var collider in nearbyObjects)
{
var colliderBox = collider.BoundingBox();
@@ -454,122 +482,6 @@ namespace NavisworksTransport.Core.Animation
}
}
- ///
- /// 获取潜在碰撞对象列表
- ///
- private List GetPotentialColliders()
- {
- try
- {
- LogManager.Info("开始获取潜在碰撞对象");
-
- // 构建通道缓存
- ClashDetectiveIntegration.Instance.BuildChannelObjectsCache();
- ClashDetectiveIntegration.BuildAllGeometryItemsCache();
-
- // 获取所有有几何体的对象
- var allItems = NavisApplication.ActiveDocument.Models.RootItemDescendantsAndSelf
- .Where(item => item.HasGeometry)
- .ToList();
-
- // 构建排除列表:动画对象本身及其子对象
- var excludeList = new ModelItemCollection();
- excludeList.AddRange(_animatedObject.DescendantsAndSelf);
-
- // 计算路径包围盒
- var pathBounds = CalculatePathBounds();
-
- // 计算路径包围盒中心点(模型单位)
- var pathCenter = new Point3D(
- (pathBounds.Min.X + pathBounds.Max.X) / 2,
- (pathBounds.Min.Y + pathBounds.Max.Y) / 2,
- (pathBounds.Min.Z + pathBounds.Max.Z) / 2
- );
-
- // 计算包围盒对角线长度
- var diagonal = Math.Sqrt(
- Math.Pow(pathBounds.Max.X - pathBounds.Min.X, 2) +
- Math.Pow(pathBounds.Max.Y - pathBounds.Min.Y, 2) +
- Math.Pow(pathBounds.Max.Z - pathBounds.Min.Z, 2)
- );
-
- // 检测半径 = 包围盒对角线的一半(精确覆盖整条路径)
- var detectionGap = diagonal / 2;
-
- LogManager.Info($"路径包围盒: Min=({pathBounds.Min.X:F2}, {pathBounds.Min.Y:F2}, {pathBounds.Min.Z:F2}), " +
- $"Max=({pathBounds.Max.X:F2}, {pathBounds.Max.Y:F2}, {pathBounds.Max.Z:F2})");
- LogManager.Info($"路径中心: ({pathCenter.X:F2}, {pathCenter.Y:F2}, {pathCenter.Z:F2})");
- LogManager.Info($"包围盒对角线: {diagonal:F2} 模型单位");
-
- // 临时移动物体到路径中心,以便在路径范围内检测潜在碰撞对象
- var offset = ModelItemTransformHelper.MoveItemToPosition(_animatedObject, pathCenter);
-
- try
- {
- LogManager.Info($"检测半径: {detectionGap:F2} 模型单位(对角线的一半)");
-
- // 调用ClashDetectiveIntegration的DetectCollisions方法
- // 现在物体在路径中心,检测路径长度半径内的对象
- // 该方法内部会自动排除通道对象(通过IsChannelObject方法)
- var collisionResults = ClashDetectiveIntegration.Instance.DetectCollisions(
- _animatedObject,
- excludeList,
- detectionGap
- );
-
- // 从碰撞结果中提取唯一的碰撞对象
- var potentialColliders = new HashSet();
- foreach (var result in collisionResults)
- {
- if (result.Item2 != null && !potentialColliders.Contains(result.Item2))
- {
- potentialColliders.Add(result.Item2);
- }
- }
-
- var finalList = potentialColliders.ToList();
-
- LogManager.Info($"获取潜在碰撞对象完成: 总对象={allItems.Count}, " +
- $"排除自身及子对象={excludeList.Count}, " +
- $"路径沿途潜在碰撞对象={finalList.Count}(已自动排除通道)");
-
- return finalList;
- }
- finally
- {
- // 恢复物体到原位置
- ModelItemTransformHelper.RestoreItemPosition(_animatedObject, offset);
- LogManager.Info("物体已恢复到原始位置");
- }
- }
- catch (Exception ex)
- {
- LogManager.Error($"获取潜在碰撞对象失败: {ex.Message}");
- return new List();
- }
- }
-
- ///
- /// 计算路径的包围盒
- ///
- private BoundingBox3D CalculatePathBounds()
- {
- if (_pathPoints == null || _pathPoints.Count == 0)
- return new BoundingBox3D();
-
- var minX = _pathPoints.Min(p => p.X);
- var minY = _pathPoints.Min(p => p.Y);
- var minZ = _pathPoints.Min(p => p.Z);
- var maxX = _pathPoints.Max(p => p.X);
- var maxY = _pathPoints.Max(p => p.Y);
- var maxZ = _pathPoints.Max(p => p.Z);
-
- return new BoundingBox3D(
- new Point3D(minX, minY, minZ),
- new Point3D(maxX, maxY, maxZ)
- );
- }
-
///
/// 创建虚拟包围盒(用于碰撞检测)
///
@@ -1959,9 +1871,17 @@ namespace NavisworksTransport.Core.Animation
public double MovementSpeed => _movementSpeed;
///
- /// 获取当前检测间隙
+ /// 获取当前检测间隙(单位:米)
///
- public double DetectionGap => _detectionGap;
+ public double DetectionGap
+ {
+ get
+ {
+ // 将内部模型单位转换为米制单位
+ var modelUnitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor();
+ return _detectionGap * modelUnitsToMeters;
+ }
+ }
///
/// 获取路径名称
diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs
index c85414c..c443e47 100644
--- a/src/Core/Collision/ClashDetectiveIntegration.cs
+++ b/src/Core/Collision/ClashDetectiveIntegration.cs
@@ -881,223 +881,6 @@ namespace NavisworksTransport
}
}
- ///
- /// 基于包围盒的快速碰撞检测(不用Clash Detective)
- ///
- public List DetectCollisions(ModelItem animatedObject,
- ModelItemCollection excludeObjects, double detectionGap)
- {
- var results = new List();
-
- try
- {
- var animatedBoundingBox = animatedObject.BoundingBox();
-
- // 性能分析计时器
- var exclusionStopwatch = new System.Diagnostics.Stopwatch();
- var getAllItemsStopwatch = new System.Diagnostics.Stopwatch();
- var excludeLogicStopwatch = new System.Diagnostics.Stopwatch();
- var boundingBoxStopwatch = new System.Diagnostics.Stopwatch();
- var intersectionStopwatch = new System.Diagnostics.Stopwatch();
- var distanceStopwatch = new System.Diagnostics.Stopwatch();
-
- // 计时:构建排除列表
- exclusionStopwatch.Start();
- var exclusionList = new List();
-
- // 1. 排除动画对象本身及其所有子对象
- exclusionList.AddRange(animatedObject.DescendantsAndSelf.Where(d => d.HasGeometry));
-
- // 2. 合并用户指定的排除对象
- if (excludeObjects != null)
- {
- foreach (var item in excludeObjects)
- {
- if (!exclusionList.Contains(item))
- {
- exclusionList.Add(item);
- }
- }
- }
- exclusionStopwatch.Stop();
-
- // 🔥 使用预构建的缓存获取对象列表
- getAllItemsStopwatch.Start();
- List itemList;
-
- lock (_cacheLock)
- {
- if (_allGeometryItemsCache != null)
- {
- // 从缓存中过滤掉动画对象本身
- itemList = _allGeometryItemsCache.Where(item => !item.Equals(animatedObject)).ToList();
- }
- else
- {
- // 缓存不存在,回退到实时获取(不应该发生,但保险起见)
- LogManager.Warning("几何对象缓存不存在,回退到实时获取(这可能影响性能)");
- var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
- .Where(item => item.HasGeometry && !item.Equals(animatedObject));
- itemList = allItems.ToList();
- }
- }
-
- getAllItemsStopwatch.Stop();
-
- int checkedCount = 0;
- int excludedCount = 0;
- int boundingBoxCallCount = 0;
- int intersectionCallCount = 0;
- int distanceCallCount = 0;
-
- foreach (var item in itemList)
- {
- try
- {
- // 计时:排除逻辑检查(现在使用缓存,应该很快)
- excludeLogicStopwatch.Start();
- bool shouldExclude = ShouldExcludeFromCollisionDetectionSimple(item, exclusionList);
- excludeLogicStopwatch.Stop();
-
- if (shouldExclude)
- {
- excludedCount++;
- continue;
- }
-
- // 计时:包围盒获取
- boundingBoxStopwatch.Start();
- var itemBoundingBox = item.BoundingBox();
- boundingBoxStopwatch.Stop();
- boundingBoxCallCount++;
-
- // 使用UI设置的检测间隙作为碰撞容差
- var tolerance = detectionGap;
-
- // 计时:相交检测
- intersectionStopwatch.Start();
- bool intersects = BoundingBoxGeometryUtils.BoundingBoxesIntersectWithTolerance(animatedBoundingBox, itemBoundingBox, tolerance);
- intersectionStopwatch.Stop();
- intersectionCallCount++;
-
- // 检查是否在容差范围内接近
- if (intersects)
- {
- // 计时:距离计算
- distanceStopwatch.Start();
- var distance = BoundingBoxGeometryUtils.CalculateDistance(animatedBoundingBox, itemBoundingBox);
- distanceStopwatch.Stop();
- distanceCallCount++;
-
- // 只有真正相交(距离为0)或在检测间隙内的才算碰撞
- if (distance <= tolerance)
- {
- // 🔧 智能容器映射:使用有名称的容器对象而非几何体子对象
- var mappedAnimatedObject = GetCollisionObjectWithValidIdentity(animatedObject);
- var mappedCollisionObject = GetCollisionObjectWithValidIdentity(item);
-
- // 检查是否进行了容器映射
- bool hasMapping1 = !mappedAnimatedObject.Equals(animatedObject);
- bool hasMapping2 = !mappedCollisionObject.Equals(item);
-
- var result = new CollisionResult
- {
- ClashGuid = Guid.NewGuid(),
- DisplayName = $"纯碰撞检测: {mappedAnimatedObject.DisplayName} <-> {mappedCollisionObject.DisplayName}",
- Status = ClashResultStatus.New,
- Item1 = mappedAnimatedObject, // 使用映射后的容器对象
- Item2 = mappedCollisionObject, // 使用映射后的容器对象
- OriginalItem1 = animatedObject, // 保存原始几何体对象
- OriginalItem2 = item, // 保存原始几何体对象
- HasContainerMapping = hasMapping1 || hasMapping2, // 标记是否进行了映射
- CreatedTime = DateTime.Now,
- Distance = distance,
- Center = BoundingBoxGeometryUtils.CalculateCenter(animatedBoundingBox, itemBoundingBox)
- };
-
- results.Add(result);
- }
- }
-
- checkedCount++;
- }
- catch (Exception itemEx)
- {
- LogManager.Warning($"检查单个对象碰撞时出错 {item.DisplayName}: {itemEx.Message}");
- }
- }
-
- if (results.Count == 0)
- {
- LogManager.Info($"未发现碰撞");
- }
-
- // 更新动画碰撞计数器
- _animationCollisionCount = results.Count;
- LogManager.Info($"动画碰撞计数器已更新: {_animationCollisionCount}");
- }
- catch (Exception ex)
- {
- LogManager.Error($"纯碰撞检测失败: {ex.Message}", ex);
- }
-
- return results;
- }
-
- ///
- /// 简化的排除判断逻辑(不依赖LogisticsAnimationManager)
- ///
- /// 要测试的对象
- /// 排除列表
- /// 如果应该排除返回true
- private bool ShouldExcludeFromCollisionDetectionSimple(ModelItem testObject, List exclusionList)
- {
- try
- {
- // 1. 基本排除列表检查
- if (exclusionList != null && exclusionList.Contains(testObject))
- {
- return true;
- }
-
- // 2. 通道对象检查(保留原有逻辑)
- if (IsChannelObject(testObject))
- {
- return true;
- }
-
- return false;
- }
- catch (Exception ex)
- {
- LogManager.Warning($"简化排除判断异常: {ex.Message}");
- return false; // 出错时保守处理,不排除
- }
- }
-
- ///
- /// 检查物体是否为通道物体(通道物体在碰撞检测时不应该变红)
- ///
- private bool IsChannelObject(ModelItem item)
- {
- try
- {
- // 使用缓存的通道对象集合
- if (_channelObjectsCache == null)
- {
- BuildChannelObjectsCache();
- }
-
- // 直接查询缓存,避免递归和重复属性查询
- return _channelObjectsCache.Contains(item);
- }
- catch (Exception ex)
- {
- LogManager.Warning($"检查通道物体时出错 {item.DisplayName}: {ex.Message}");
- return false;
- }
- }
-
///
/// 构建通道对象缓存,一次性扫描所有对象,避免重复的属性查询
///
@@ -1176,9 +959,6 @@ namespace NavisworksTransport
}
- ///
- /// 构建几何对象列表缓存,一次性获取所有几何对象
- ///
///
/// 构建几何对象列表缓存,一次性获取所有几何对象
///
@@ -1187,17 +967,17 @@ namespace NavisworksTransport
lock (_cacheLock)
{
if (_allGeometryItemsCache != null) return; // 双重检查锁定
-
+
var cacheStopwatch = new System.Diagnostics.Stopwatch();
cacheStopwatch.Start();
-
+
try
{
var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(item => item.HasGeometry);
-
+
_allGeometryItemsCache = allItems.ToList();
-
+
cacheStopwatch.Stop();
LogManager.Info($"几何对象列表缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
LogManager.Info($" - 缓存对象总数: {_allGeometryItemsCache.Count} 个");
@@ -1209,6 +989,38 @@ namespace NavisworksTransport
}
}
}
+
+ ///
+ /// 获取几何对象缓存(供外部使用)
+ ///
+ /// 几何对象列表的副本,如果缓存不存在则返回null
+ public static List GetAllGeometryItemsCache()
+ {
+ lock (_cacheLock)
+ {
+ return _allGeometryItemsCache?.ToList(); // 返回副本以保证线程安全
+ }
+ }
+
+ ///
+ /// 获取已排除通道对象的几何对象缓存(供空间索引使用)
+ ///
+ /// 排除通道后的几何对象列表,如果缓存不存在则返回null
+ public static List GetNonChannelGeometryItemsCache()
+ {
+ lock (_cacheLock)
+ {
+ if (_allGeometryItemsCache == null || _channelObjectsCache == null)
+ {
+ return null;
+ }
+
+ // 直接过滤掉通道对象
+ return _allGeometryItemsCache
+ .Where(item => !_channelObjectsCache.Contains(item))
+ .ToList();
+ }
+ }
///
/// 清除所有缓存,在模型变化时调用
diff --git a/src/Core/Spatial/SpatialHashGrid.cs b/src/Core/Spatial/SpatialHashGrid.cs
new file mode 100644
index 0000000..446c030
--- /dev/null
+++ b/src/Core/Spatial/SpatialHashGrid.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using g4; // geometry4Sharp
+
+namespace NavisworksTransport.Core.Spatial
+{
+ ///
+ /// 自定义3D空间哈希网格
+ /// 用于高效的空间范围查询,复杂度 O(1) 平均
+ ///
+ /// 设计参考 geometry3Sharp 的 PointHashGrid3d,但提供了以下增强:
+ /// 1. 支持返回范围内的所有对象(而不仅是最近的一个)
+ /// 2. 支持直接访问网格单元(GetObjectsInCell)
+ /// 3. 提供详细的统计信息用于性能分析
+ /// 4. 使用 ScaleGridIndexer3 实现更清晰的坐标转换
+ ///
+ /// 存储的对象类型
+ public class SpatialHashGrid
+ {
+ private readonly Dictionary> _grid;
+ private readonly ScaleGridIndexer3 _indexer;
+
+ ///
+ /// 创建空间哈希网格
+ ///
+ /// 格子大小(模型单位)
+ public SpatialHashGrid(double cellSize)
+ {
+ if (cellSize <= 0)
+ throw new ArgumentException("格子大小必须大于0", nameof(cellSize));
+
+ _indexer = new ScaleGridIndexer3() { CellSize = cellSize };
+ _grid = new Dictionary>();
+ }
+
+ ///
+ /// 格子大小(模型单位)
+ ///
+ public double CellSize => _indexer.CellSize;
+
+ ///
+ /// 格子总数
+ ///
+ public int CellCount => _grid.Count;
+
+ ///
+ /// 对象总数
+ ///
+ public int ObjectCount
+ {
+ get
+ {
+ int count = 0;
+ foreach (var list in _grid.Values)
+ {
+ count += list.Count;
+ }
+ return count;
+ }
+ }
+
+ ///
+ /// 插入对象到空间哈希网格
+ ///
+ /// 要插入的对象
+ /// 对象的3D位置
+ public void Insert(T item, Vector3d position)
+ {
+ var gridCell = _indexer.ToGrid(position);
+
+ if (!_grid.TryGetValue(gridCell, out var cellList))
+ {
+ cellList = new List();
+ _grid[gridCell] = cellList;
+ }
+
+ cellList.Add(item);
+ }
+
+ ///
+ /// 批量插入对象
+ ///
+ /// 对象和位置的键值对
+ public void InsertRange(IEnumerable> items)
+ {
+ foreach (var kvp in items)
+ {
+ Insert(kvp.Key, kvp.Value);
+ }
+ }
+
+ ///
+ /// 获取指定格子中的所有对象
+ ///
+ /// 格子坐标
+ /// 该格子中的对象列表
+ public List GetObjectsInCell(Vector3i gridCell)
+ {
+ if (_grid.TryGetValue(gridCell, out var list))
+ {
+ return new List(list); // 返回副本,避免外部修改
+ }
+ return new List();
+ }
+
+ ///
+ /// 范围查询:查找指定位置附近指定半径内的所有对象
+ ///
+ /// 查询中心位置
+ /// 查询半径(模型单位)
+ /// 可选的距离计算函数(用于精确过滤)
+ /// 范围内的对象列表(去重)
+ public List FindInRadius(
+ Vector3d center,
+ double radius,
+ Func distanceFunc = null)
+ {
+ var results = new HashSet();
+
+ // 计算需要检查的格子范围
+ int gridRadius = (int)Math.Ceiling(radius / _indexer.CellSize);
+ var centerGrid = _indexer.ToGrid(center);
+
+ // 优化:提前创建忽略函数,避免在循环中检查 null(借鉴 PointHashGrid3d)
+ bool hasDistanceCheck = (distanceFunc != null);
+
+ // 遍历查询范围内的所有格子
+ for (int dx = -gridRadius; dx <= gridRadius; dx++)
+ {
+ for (int dy = -gridRadius; dy <= gridRadius; dy++)
+ {
+ for (int dz = -gridRadius; dz <= gridRadius; dz++)
+ {
+ var gridCell = new Vector3i(
+ centerGrid.x + dx,
+ centerGrid.y + dy,
+ centerGrid.z + dz
+ );
+
+ // 使用 TryGetValue 避免两次字典查找(借鉴 PointHashGrid3d)
+ if (_grid.TryGetValue(gridCell, out var cellObjects))
+ {
+ foreach (var obj in cellObjects)
+ {
+ // 如果提供了距离函数,进行精确距离检查
+ if (hasDistanceCheck)
+ {
+ double distance = distanceFunc(obj);
+ if (distance <= radius)
+ {
+ results.Add(obj);
+ }
+ }
+ else
+ {
+ // 否则直接添加(假设格子内的对象都在范围内)
+ results.Add(obj);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return new List(results);
+ }
+
+ ///
+ /// 范围查询(带精确距离检查)
+ ///
+ /// 查询中心
+ /// 查询半径
+ /// 获取对象位置的函数
+ /// 范围内的对象列表
+ public List FindInRadiusExact(
+ Vector3d center,
+ double radius,
+ Func getPositionFunc)
+ {
+ if (getPositionFunc == null)
+ throw new ArgumentNullException(nameof(getPositionFunc));
+
+ return FindInRadius(center, radius, obj =>
+ {
+ var objPos = getPositionFunc(obj);
+ return center.Distance(objPos);
+ });
+ }
+
+ ///
+ /// 清空所有数据
+ ///
+ public void Clear()
+ {
+ _grid.Clear();
+ }
+
+ ///
+ /// 获取网格统计信息(用于调试和性能分析)
+ ///
+ /// 统计信息字符串
+ public string GetStatistics()
+ {
+ int totalObjects = 0;
+ int maxObjectsPerCell = 0;
+ int minObjectsPerCell = int.MaxValue;
+ int emptyCount = 0;
+
+ foreach (var list in _grid.Values)
+ {
+ int count = list.Count;
+ totalObjects += count;
+
+ if (count == 0)
+ emptyCount++;
+
+ if (count > maxObjectsPerCell)
+ maxObjectsPerCell = count;
+
+ if (count < minObjectsPerCell && count > 0)
+ minObjectsPerCell = count;
+ }
+
+ double avgObjectsPerCell = _grid.Count > 0 ? (double)totalObjects / _grid.Count : 0;
+
+ if (minObjectsPerCell == int.MaxValue)
+ minObjectsPerCell = 0;
+
+ return $"空间哈希网格统计:\n" +
+ $" 格子大小: {_indexer.CellSize:F2} 模型单位\n" +
+ $" 总格子数: {_grid.Count}\n" +
+ $" 总对象数: {totalObjects}\n" +
+ $" 平均每格对象数: {avgObjectsPerCell:F2}\n" +
+ $" 最大每格对象数: {maxObjectsPerCell}\n" +
+ $" 最小每格对象数: {minObjectsPerCell}\n" +
+ $" 空格子数: {emptyCount}";
+ }
+ }
+}
diff --git a/src/Core/Spatial/SpatialIndexManager.cs b/src/Core/Spatial/SpatialIndexManager.cs
new file mode 100644
index 0000000..670c666
--- /dev/null
+++ b/src/Core/Spatial/SpatialIndexManager.cs
@@ -0,0 +1,301 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Autodesk.Navisworks.Api;
+using NavisworksTransport.Utils;
+using g4; // geometry4Sharp
+
+// 使用命名空间引用以避免类名冲突
+using ClashIntegration = NavisworksTransport.ClashDetectiveIntegration;
+
+namespace NavisworksTransport.Core.Spatial
+{
+ ///
+ /// 全局空间索引管理器
+ /// 单例模式,为所有动画提供高效的空间查询服务
+ ///
+ public class SpatialIndexManager
+ {
+ private static SpatialIndexManager _instance;
+ private static readonly object _lockObj = new object();
+
+ ///
+ /// 单例实例
+ ///
+ public static SpatialIndexManager Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ lock (_lockObj)
+ {
+ if (_instance == null)
+ {
+ _instance = new SpatialIndexManager();
+ }
+ }
+ }
+ return _instance;
+ }
+ }
+
+ private SpatialHashGrid _globalSpatialIndex;
+ private double _cellSize = 1.0; // 默认格子大小(模型单位)
+ private bool _isInitialized = false;
+ private int _indexedObjectCount = 0;
+
+ // 缓存对象位置,避免重复计算
+ private readonly Dictionary _objectPositions = new Dictionary();
+
+ private SpatialIndexManager()
+ {
+ }
+
+ ///
+ /// 空间索引是否已初始化
+ ///
+ public bool IsInitialized => _isInitialized;
+
+ ///
+ /// 格子大小(模型单位)
+ ///
+ public double CellSize => _cellSize;
+
+ ///
+ /// 已索引的对象数量
+ ///
+ public int IndexedObjectCount => _indexedObjectCount;
+
+ ///
+ /// 构建全局空间索引
+ ///
+ /// 格子大小(模型单位),建议设置为车辆半径的2倍
+ public void BuildGlobalIndex(double cellSizeInModelUnits = 1.0)
+ {
+ var sw = Stopwatch.StartNew();
+ LogManager.Info("[空间索引] 开始构建全局空间索引");
+ LogManager.Info($"[空间索引] 格子大小: {cellSizeInModelUnits:F2} 模型单位");
+
+ try
+ {
+ _cellSize = cellSizeInModelUnits;
+
+ // 1. 直接从缓存获取已排除通道的几何对象(调用方已在动画生成阶段构建缓存)
+ var nonChannelItems = ClashIntegration.GetNonChannelGeometryItemsCache();
+
+ if (nonChannelItems == null)
+ {
+ // 缓存不存在,说明调用方未按预期构建缓存,这是逻辑错误
+ LogManager.Error("[空间索引] 几何对象缓存或通道缓存不存在!调用方应在动画生成阶段构建缓存。");
+ throw new InvalidOperationException("空间索引构建失败:几何对象缓存未初始化。请先调用 BuildAllGeometryItemsCache() 和 BuildChannelObjectsCache()。");
+ }
+
+ LogManager.Info($"[空间索引] 从缓存获取 {nonChannelItems.Count} 个非通道几何对象(已过滤通道)");
+
+ if (nonChannelItems.Count == 0)
+ {
+ LogManager.Warning("[空间索引] 场景中没有非通道几何对象");
+ return;
+ }
+
+ // 2. 创建空间哈希网格
+ _globalSpatialIndex = new SpatialHashGrid(cellSizeInModelUnits);
+ _objectPositions.Clear();
+
+ // 3. 索引所有非通道对象
+ int indexedCount = 0;
+ int failedCount = 0;
+
+ foreach (var item in nonChannelItems)
+ {
+ try
+ {
+ // 获取包围盒中心作为对象位置
+ var bbox = item.BoundingBox();
+ var center = new Vector3d(
+ (bbox.Min.X + bbox.Max.X) / 2.0,
+ (bbox.Min.Y + bbox.Max.Y) / 2.0,
+ (bbox.Min.Z + bbox.Max.Z) / 2.0
+ );
+
+ // 插入到空间索引
+ _globalSpatialIndex.Insert(item, center);
+
+ // 缓存位置
+ _objectPositions[item] = center;
+
+ indexedCount++;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Warning($"[空间索引] 索引对象失败: {item.DisplayName}, 错误: {ex.Message}");
+ failedCount++;
+ }
+ }
+
+ _indexedObjectCount = indexedCount;
+ _isInitialized = true;
+
+ sw.Stop();
+
+ LogManager.Info("[空间索引] 构建完成");
+ LogManager.Info($" - 成功索引: {indexedCount} 个对象(通道已在缓存阶段过滤)");
+ LogManager.Info($" - 失败: {failedCount} 个对象");
+ LogManager.Info($" - 格子数量: {_globalSpatialIndex.CellCount} 个");
+ LogManager.Info($" - 耗时: {sw.ElapsedMilliseconds} ms");
+
+ // 输出统计信息
+ LogManager.Debug("[空间索引] " + _globalSpatialIndex.GetStatistics());
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[空间索引] 构建失败: {ex.Message}");
+ LogManager.Error($"[空间索引] 堆栈跟踪: {ex.StackTrace}");
+ _isInitialized = false;
+ throw;
+ }
+ }
+
+ ///
+ /// 范围查询:查找指定位置附近的对象
+ ///
+ /// 查询中心位置(Navisworks Point3D)
+ /// 查询半径(模型单位)
+ /// 要排除的对象(通常是移动物体本身)
+ /// 范围内的对象列表
+ public List FindNearbyObjects(
+ Point3D position,
+ double searchRadiusInModelUnits,
+ ModelItem excludeObject = null)
+ {
+ if (!_isInitialized)
+ {
+ throw new InvalidOperationException(
+ "[空间索引] 空间索引未初始化,请先调用 BuildGlobalIndex()");
+ }
+
+ var sw = Stopwatch.StartNew();
+
+ try
+ {
+ // 转换为 geometry4Sharp 的 Vector3d
+ var centerVec = new Vector3d(position.X, position.Y, position.Z);
+
+ // 使用空间哈希网格进行范围查询(带精确距离检查)
+ var nearbyObjects = _globalSpatialIndex.FindInRadiusExact(
+ centerVec,
+ searchRadiusInModelUnits,
+ getPositionFunc: item =>
+ {
+ // 从缓存中获取对象位置
+ if (_objectPositions.TryGetValue(item, out var pos))
+ {
+ return pos;
+ }
+
+ // 缓存未命中,重新计算(理论上不应该发生)
+ var bbox = item.BoundingBox();
+ return new Vector3d(
+ (bbox.Min.X + bbox.Max.X) / 2.0,
+ (bbox.Min.Y + bbox.Max.Y) / 2.0,
+ (bbox.Min.Z + bbox.Max.Z) / 2.0
+ );
+ }
+ );
+
+ // 排除指定对象
+ if (excludeObject != null)
+ {
+ nearbyObjects = nearbyObjects.Where(obj => !obj.Equals(excludeObject)).ToList();
+ }
+
+ sw.Stop();
+
+ LogManager.Debug($"[空间索引] 范围查询完成: " +
+ $"位置=({position.X:F2}, {position.Y:F2}, {position.Z:F2}), " +
+ $"半径={searchRadiusInModelUnits:F2}, " +
+ $"结果={nearbyObjects.Count} 个对象, " +
+ $"耗时={sw.Elapsed.TotalMilliseconds:F2}ms");
+
+ return nearbyObjects;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[空间索引] 范围查询失败: {ex.Message}");
+ throw;
+ }
+ }
+
+ ///
+ /// 范围查询(带排除列表)
+ ///
+ /// 查询中心位置
+ /// 查询半径
+ /// 要排除的对象列表
+ /// 范围内的对象列表
+ public List FindNearbyObjects(
+ Point3D position,
+ double searchRadiusInModelUnits,
+ IEnumerable excludeObjects)
+ {
+ var results = FindNearbyObjects(position, searchRadiusInModelUnits, excludeObject: null);
+
+ if (excludeObjects != null)
+ {
+ var excludeSet = new HashSet(excludeObjects);
+ results = results.Where(obj => !excludeSet.Contains(obj)).ToList();
+ }
+
+ return results;
+ }
+
+ ///
+ /// 获取对象的缓存位置
+ ///
+ /// 模型对象
+ /// 对象的3D位置(Vector3d)
+ public Vector3d? GetObjectPosition(ModelItem item)
+ {
+ if (_objectPositions.TryGetValue(item, out var pos))
+ {
+ return pos;
+ }
+ return null;
+ }
+
+ ///
+ /// 清除空间索引
+ ///
+ public void Clear()
+ {
+ LogManager.Info("[空间索引] 清除空间索引");
+
+ _globalSpatialIndex?.Clear();
+ _objectPositions.Clear();
+ _isInitialized = false;
+ _indexedObjectCount = 0;
+
+ LogManager.Info("[空间索引] 空间索引已清除");
+ }
+
+ ///
+ /// 获取空间索引统计信息
+ ///
+ /// 统计信息字符串
+ public string GetStatistics()
+ {
+ if (!_isInitialized)
+ {
+ return "[空间索引] 未初始化";
+ }
+
+ return $"[空间索引] 统计信息:\n" +
+ $" - 已索引对象: {_indexedObjectCount} 个\n" +
+ $" - 格子大小: {_cellSize:F2} 模型单位\n" +
+ $" - 格子数量: {_globalSpatialIndex.CellCount} 个\n" +
+ $"{_globalSpatialIndex.GetStatistics()}";
+ }
+ }
+}
diff --git a/src/UI/WPF/Collections/ThreadSafeObservableCollection.cs b/src/UI/WPF/Collections/ThreadSafeObservableCollection.cs
index 9fcb0fd..52e5292 100644
--- a/src/UI/WPF/Collections/ThreadSafeObservableCollection.cs
+++ b/src/UI/WPF/Collections/ThreadSafeObservableCollection.cs
@@ -30,7 +30,6 @@ namespace NavisworksTransport.UI.WPF.Collections
private readonly object _lock = new object();
private readonly SynchronizationContext _synchronizationContext;
private volatile bool _suppressNotification = false;
- private readonly DataBindingPerformanceMonitor _performanceMonitor;
///
/// 获取当前是否正在批量更新中(禁用通知)
@@ -62,11 +61,10 @@ namespace NavisworksTransport.UI.WPF.Collections
: base()
{
_synchronizationContext = SynchronizationContext.Current;
- _performanceMonitor = DataBindingPerformanceMonitor.Instance;
-
+
// 启用WPF集合同步机制
BindingOperations.EnableCollectionSynchronization(this, _lock);
-
+
LogManager.Debug($"ThreadSafeObservableCollection<{typeof(T).Name}>已初始化");
}
@@ -78,11 +76,10 @@ namespace NavisworksTransport.UI.WPF.Collections
: base(collection)
{
_synchronizationContext = SynchronizationContext.Current;
- _performanceMonitor = DataBindingPerformanceMonitor.Instance;
-
+
// 启用WPF集合同步机制
BindingOperations.EnableCollectionSynchronization(this, _lock);
-
+
LogManager.Debug($"ThreadSafeObservableCollection<{typeof(T).Name}>已初始化,包含{Count}个元素");
}
@@ -206,36 +203,33 @@ namespace NavisworksTransport.UI.WPF.Collections
return;
}
- using (_performanceMonitor.BeginCollectionUpdate(typeof(ThreadSafeObservableCollection), "AddRange", itemList.Count))
+ lock (_lock)
{
- lock (_lock)
+ try
{
- try
- {
- // 禁用通知
- _suppressNotification = true;
+ // 禁用通知
+ _suppressNotification = true;
- // 批量添加
- foreach (var item in itemList)
+ // 批量添加
+ foreach (var item in itemList)
+ {
+ if (item != null)
{
- if (item != null)
- {
- base.Add(item);
- }
+ base.Add(item);
}
}
- finally
- {
- // 恢复通知
- _suppressNotification = false;
- }
-
- // 触发批量添加事件
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(
- NotifyCollectionChangedAction.Add,
- itemList,
- Count - itemList.Count));
}
+ finally
+ {
+ // 恢复通知
+ _suppressNotification = false;
+ }
+
+ // 触发批量添加事件
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(
+ NotifyCollectionChangedAction.Add,
+ itemList,
+ Count - itemList.Count));
}
LogManager.Debug($"ThreadSafeObservableCollection批量添加{itemList.Count}个元素");
diff --git a/src/UI/WPF/Services/BindingExpressionOptimizer.cs b/src/UI/WPF/Services/BindingExpressionOptimizer.cs
index 95d2592..659ba59 100644
--- a/src/UI/WPF/Services/BindingExpressionOptimizer.cs
+++ b/src/UI/WPF/Services/BindingExpressionOptimizer.cs
@@ -25,9 +25,6 @@ namespace NavisworksTransport.UI.WPF.Services
private readonly ConcurrentDictionary _bindingCache;
private readonly ConcurrentDictionary _bindingMetadata;
- // 性能监控
- private readonly DataBindingPerformanceMonitor _performanceMonitor;
-
// 优化配置
private TimeSpan _cacheExpiration = TimeSpan.FromMinutes(5);
private int _maxCacheSize = 1000;
@@ -125,7 +122,6 @@ namespace NavisworksTransport.UI.WPF.Services
_bindingCache = new ConcurrentDictionary();
_bindingMetadata = new ConcurrentDictionary();
_pendingUpdates = new HashSet();
- _performanceMonitor = DataBindingPerformanceMonitor.Instance;
// 初始化批量更新定时器
_batchUpdateTimer = new DispatcherTimer(DispatcherPriority.Background)
@@ -292,10 +288,7 @@ namespace NavisworksTransport.UI.WPF.Services
try
{
- using (_performanceMonitor.BeginPropertyUpdate(bindingExpression.Target?.GetType() ?? typeof(object), GetBindingPath(bindingExpression)))
- {
- bindingExpression.UpdateTarget();
- }
+ bindingExpression.UpdateTarget();
// 更新元数据
if (_bindingMetadata.TryGetValue(bindingExpression, out var metadata))
@@ -336,16 +329,13 @@ namespace NavisworksTransport.UI.WPF.Services
try
{
- using (_performanceMonitor.BeginCollectionUpdate(typeof(BindingExpressionOptimizer), "BatchUpdate", updatesToProcess.Count))
- {
- // 按优先级和类型分组
- var groupedUpdates = GroupUpdatesByPriority(updatesToProcess);
+ // 按优先级和类型分组
+ var groupedUpdates = GroupUpdatesByPriority(updatesToProcess);
- // 按优先级顺序处理
- foreach (var group in groupedUpdates.OrderByDescending(g => g.Key))
- {
- ProcessUpdateGroup(group.Value);
- }
+ // 按优先级顺序处理
+ foreach (var group in groupedUpdates.OrderByDescending(g => g.Key))
+ {
+ ProcessUpdateGroup(group.Value);
}
LogManager.Debug($"批量更新完成: {updatesToProcess.Count}个绑定");
diff --git a/src/UI/WPF/Services/DataBindingPerformanceMonitor.cs b/src/UI/WPF/Services/DataBindingPerformanceMonitor.cs
deleted file mode 100644
index 5364085..0000000
--- a/src/UI/WPF/Services/DataBindingPerformanceMonitor.cs
+++ /dev/null
@@ -1,695 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows.Threading;
-
-namespace NavisworksTransport.UI.WPF.Services
-{
- ///
- /// 数据绑定性能监控器 - 监控和分析WPF数据绑定的性能表现
- /// 提供详细的性能指标收集和优化建议
- ///
- public class DataBindingPerformanceMonitor : IDisposable
- {
- #region 字段和属性
-
- private static DataBindingPerformanceMonitor _instance;
- private static readonly object _instanceLock = new object();
-
- // 性能数据收集
- private readonly ConcurrentDictionary _propertyMetrics;
- private readonly ConcurrentDictionary _collectionMetrics;
- private readonly ConcurrentQueue _updateEvents;
- private readonly Timer _reportTimer;
- private volatile bool _isDisposed = false;
- private volatile bool _isMonitoringEnabled = true;
-
- // 监控配置
- private TimeSpan _reportInterval = TimeSpan.FromMinutes(5);
- private int _maxEventHistory = 10000;
- private int _performanceThresholdMs = 16; // 60FPS下每帧约16ms
-
- ///
- /// 获取性能监控器的单例实例
- ///
- public static DataBindingPerformanceMonitor Instance
- {
- get
- {
- if (_instance == null)
- {
- lock (_instanceLock)
- {
- if (_instance == null)
- {
- _instance = new DataBindingPerformanceMonitor();
- }
- }
- }
- return _instance;
- }
- }
-
- ///
- /// 是否启用性能监控
- ///
- public bool IsMonitoringEnabled
- {
- get => _isMonitoringEnabled;
- set => _isMonitoringEnabled = value;
- }
-
- ///
- /// 性能阈值(毫秒)- 超过此值的操作将被标记为慢操作
- ///
- public int PerformanceThresholdMs
- {
- get => _performanceThresholdMs;
- set => _performanceThresholdMs = Math.Max(1, value);
- }
-
- ///
- /// 当前监控的属性数量
- ///
- public int MonitoredPropertiesCount => _propertyMetrics.Count;
-
- ///
- /// 当前监控的集合数量
- ///
- public int MonitoredCollectionsCount => _collectionMetrics.Count;
-
- #endregion
-
- #region 构造函数和初始化
-
- private DataBindingPerformanceMonitor()
- {
- _propertyMetrics = new ConcurrentDictionary();
- _collectionMetrics = new ConcurrentDictionary();
- _updateEvents = new ConcurrentQueue();
-
- // 定期生成性能报告
- _reportTimer = new Timer(GeneratePerformanceReport, null, _reportInterval, _reportInterval);
-
- LogManager.Info("数据绑定性能监控器已初始化");
- }
-
- #endregion
-
- #region 核心监控接口
-
- ///
- /// 记录属性变更事件的开始
- ///
- /// ViewModel类型
- /// 属性名称
- /// 性能测量上下文
- public IDisposable BeginPropertyUpdate(Type viewModelType, string propertyName)
- {
- if (!_isMonitoringEnabled || _isDisposed)
- return new EmptyDisposable();
-
- var key = $"{viewModelType.Name}.{propertyName}";
- var stopwatch = Stopwatch.StartNew();
-
- return new PropertyUpdateContext(this, key, stopwatch, Dispatcher.CurrentDispatcher);
- }
-
- ///
- /// 记录集合变更事件的开始
- ///
- /// 集合类型
- /// 操作类型(Add, Remove, Clear等)
- /// 影响的项目数量
- /// 性能测量上下文
- public IDisposable BeginCollectionUpdate(Type collectionType, string operationType, int itemCount = 1)
- {
- if (!_isMonitoringEnabled || _isDisposed)
- return new EmptyDisposable();
-
- var key = $"{collectionType.Name}.{operationType}";
- var stopwatch = Stopwatch.StartNew();
-
- return new CollectionUpdateContext(this, key, stopwatch, itemCount, Dispatcher.CurrentDispatcher);
- }
-
- ///
- /// 记录绑定更新事件
- ///
- /// 绑定路径
- /// 更新类型
- /// 更新耗时(毫秒)
- public void RecordBindingUpdate(string bindingPath, string updateType, double durationMs)
- {
- if (!_isMonitoringEnabled || _isDisposed)
- return;
-
- var updateEvent = new BindingUpdateEvent
- {
- Timestamp = DateTime.UtcNow,
- BindingPath = bindingPath,
- UpdateType = updateType,
- DurationMs = durationMs,
- IsSlowUpdate = durationMs > _performanceThresholdMs,
- ThreadId = Thread.CurrentThread.ManagedThreadId
- };
-
- _updateEvents.Enqueue(updateEvent);
-
- // 限制事件历史记录数量
- while (_updateEvents.Count > _maxEventHistory)
- {
- _updateEvents.TryDequeue(out _);
- }
-
- // 记录慢更新警告
- if (updateEvent.IsSlowUpdate)
- {
- LogManager.Warning($"检测到慢数据绑定更新: {bindingPath} ({updateType}) - {durationMs:F2}ms");
- }
- }
-
- #endregion
-
- #region 内部方法
-
- ///
- /// 完成属性更新监控
- ///
- internal void EndPropertyUpdate(string key, Stopwatch stopwatch, Dispatcher dispatcher)
- {
- if (_isDisposed) return;
-
- stopwatch.Stop();
- var durationMs = stopwatch.Elapsed.TotalMilliseconds;
-
- var metrics = _propertyMetrics.GetOrAdd(key, _ => new PropertyUpdateMetrics(key));
- metrics.RecordUpdate(durationMs, dispatcher == Dispatcher.CurrentDispatcher);
-
- RecordBindingUpdate(key, "PropertyChanged", durationMs);
- }
-
- ///
- /// 完成集合更新监控
- ///
- internal void EndCollectionUpdate(string key, Stopwatch stopwatch, int itemCount, Dispatcher dispatcher)
- {
- if (_isDisposed) return;
-
- stopwatch.Stop();
- var durationMs = stopwatch.Elapsed.TotalMilliseconds;
-
- var metrics = _collectionMetrics.GetOrAdd(key, _ => new CollectionUpdateMetrics(key));
- metrics.RecordUpdate(durationMs, itemCount, dispatcher == Dispatcher.CurrentDispatcher);
-
- RecordBindingUpdate(key, "CollectionChanged", durationMs);
- }
-
- ///
- /// 生成性能报告
- ///
- private void GeneratePerformanceReport(object state)
- {
- if (_isDisposed) return;
-
- try
- {
- var report = GenerateDetailedReport();
- LogManager.Info($"数据绑定性能报告:\n{report}");
-
- // 清理过期数据
- CleanupExpiredData();
- }
- catch (Exception ex)
- {
- LogManager.Error($"生成数据绑定性能报告失败: {ex.Message}");
- }
- }
-
- ///
- /// 清理过期数据
- ///
- private void CleanupExpiredData()
- {
- var cutoffTime = DateTime.UtcNow.Subtract(TimeSpan.FromHours(1));
-
- // 清理属性指标中的过期数据
- foreach (var metrics in _propertyMetrics.Values)
- {
- metrics.CleanupOldData(cutoffTime);
- }
-
- // 清理集合指标中的过期数据
- foreach (var metrics in _collectionMetrics.Values)
- {
- metrics.CleanupOldData(cutoffTime);
- }
- }
-
- #endregion
-
- #region 报告生成
-
- ///
- /// 生成详细的性能报告
- ///
- /// 性能报告字符串
- public string GenerateDetailedReport()
- {
- var report = new System.Text.StringBuilder();
-
- report.AppendLine("=== 数据绑定性能监控报告 ===");
- report.AppendLine($"报告时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
- report.AppendLine($"监控状态: {(IsMonitoringEnabled ? "启用" : "禁用")}");
- report.AppendLine($"性能阈值: {PerformanceThresholdMs}ms");
- report.AppendLine();
-
- // 属性更新统计
- report.AppendLine("属性更新性能统计:");
- var topSlowProperties = _propertyMetrics.Values
- .Where(m => m.UpdateCount > 0)
- .OrderByDescending(m => m.AverageUpdateTime)
- .Take(10)
- .ToList();
-
- if (topSlowProperties.Any())
- {
- foreach (var metrics in topSlowProperties)
- {
- report.AppendLine($" {metrics.PropertyKey}:");
- report.AppendLine($" 更新次数: {metrics.UpdateCount}");
- report.AppendLine($" 平均耗时: {metrics.AverageUpdateTime:F2}ms");
- report.AppendLine($" 最大耗时: {metrics.MaxUpdateTime:F2}ms");
- report.AppendLine($" 慢更新次数: {metrics.SlowUpdateCount}");
- report.AppendLine($" 跨线程更新次数: {metrics.CrossThreadUpdateCount}");
- }
- }
- else
- {
- report.AppendLine(" 暂无属性更新数据");
- }
-
- report.AppendLine();
-
- // 集合更新统计
- report.AppendLine("集合更新性能统计:");
- var topSlowCollections = _collectionMetrics.Values
- .Where(m => m.UpdateCount > 0)
- .OrderByDescending(m => m.AverageUpdateTime)
- .Take(10)
- .ToList();
-
- if (topSlowCollections.Any())
- {
- foreach (var metrics in topSlowCollections)
- {
- report.AppendLine($" {metrics.CollectionKey}:");
- report.AppendLine($" 更新次数: {metrics.UpdateCount}");
- report.AppendLine($" 平均耗时: {metrics.AverageUpdateTime:F2}ms");
- report.AppendLine($" 最大耗时: {metrics.MaxUpdateTime:F2}ms");
- report.AppendLine($" 处理项目总数: {metrics.TotalItemsProcessed}");
- report.AppendLine($" 慢更新次数: {metrics.SlowUpdateCount}");
- }
- }
- else
- {
- report.AppendLine(" 暂无集合更新数据");
- }
-
- report.AppendLine();
-
- // 最近的慢更新事件
- var recentSlowUpdates = _updateEvents
- .Where(e => e.IsSlowUpdate && e.Timestamp > DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(10)))
- .OrderByDescending(e => e.DurationMs)
- .Take(5)
- .ToList();
-
- if (recentSlowUpdates.Any())
- {
- report.AppendLine("最近的慢更新事件 (10分钟内):");
- foreach (var evt in recentSlowUpdates)
- {
- report.AppendLine($" {evt.BindingPath} ({evt.UpdateType}) - {evt.DurationMs:F2}ms @ {evt.Timestamp:HH:mm:ss}");
- }
- }
-
- // 性能优化建议
- report.AppendLine();
- report.AppendLine("性能优化建议:");
-
- var suggestions = GenerateOptimizationSuggestions();
- if (suggestions.Any())
- {
- foreach (var suggestion in suggestions)
- {
- report.AppendLine($" • {suggestion}");
- }
- }
- else
- {
- report.AppendLine(" 当前性能表现良好,暂无优化建议");
- }
-
- return report.ToString();
- }
-
- ///
- /// 生成性能优化建议
- ///
- /// 优化建议列表
- public List GenerateOptimizationSuggestions()
- {
- var suggestions = new List();
-
- // 检查频繁更新的属性
- var frequentProperties = _propertyMetrics.Values
- .Where(m => m.UpdateCount > 100 && m.AverageUpdateTime > _performanceThresholdMs)
- .ToList();
-
- if (frequentProperties.Any())
- {
- suggestions.Add($"发现{frequentProperties.Count}个高频慢更新属性,建议考虑使用延迟更新或批量更新机制");
- }
-
- // 检查大集合操作
- var largeCollectionUpdates = _collectionMetrics.Values
- .Where(m => m.AverageItemsPerUpdate > 1000 && m.AverageUpdateTime > _performanceThresholdMs * 5)
- .ToList();
-
- if (largeCollectionUpdates.Any())
- {
- suggestions.Add($"发现{largeCollectionUpdates.Count}个大集合更新操作,建议实现虚拟化或分页机制");
- }
-
- // 检查跨线程更新
- var crossThreadUpdates = _propertyMetrics.Values
- .Where(m => m.CrossThreadUpdateCount > m.UpdateCount * 0.1) // 超过10%的跨线程更新
- .ToList();
-
- if (crossThreadUpdates.Any())
- {
- suggestions.Add($"发现{crossThreadUpdates.Count}个属性存在频繁的跨线程更新,建议优化线程调度");
- }
-
- // 检查内存使用
- var totalMetrics = _propertyMetrics.Count + _collectionMetrics.Count;
- if (totalMetrics > 1000)
- {
- suggestions.Add("监控的绑定数量较多,建议定期清理不再使用的绑定以释放内存");
- }
-
- return suggestions;
- }
-
- ///
- /// 获取性能概览数据
- ///
- /// 性能概览
- public PerformanceOverview GetPerformanceOverview()
- {
- var totalPropertyUpdates = _propertyMetrics.Values.Sum(m => m.UpdateCount);
- var totalCollectionUpdates = _collectionMetrics.Values.Sum(m => m.UpdateCount);
- var totalSlowUpdates = _updateEvents.Count(e => e.IsSlowUpdate);
-
- var avgPropertyUpdateTime = _propertyMetrics.Values
- .Where(m => m.UpdateCount > 0)
- .DefaultIfEmpty(new PropertyUpdateMetrics("dummy"))
- .Average(m => m.AverageUpdateTime);
-
- var avgCollectionUpdateTime = _collectionMetrics.Values
- .Where(m => m.UpdateCount > 0)
- .DefaultIfEmpty(new CollectionUpdateMetrics("dummy"))
- .Average(m => m.AverageUpdateTime);
-
- return new PerformanceOverview
- {
- TotalPropertyUpdates = totalPropertyUpdates,
- TotalCollectionUpdates = totalCollectionUpdates,
- TotalSlowUpdates = totalSlowUpdates,
- AveragePropertyUpdateTime = avgPropertyUpdateTime,
- AverageCollectionUpdateTime = avgCollectionUpdateTime,
- MonitoredPropertiesCount = MonitoredPropertiesCount,
- MonitoredCollectionsCount = MonitoredCollectionsCount,
- IsHealthy = totalSlowUpdates < (totalPropertyUpdates + totalCollectionUpdates) * 0.05 // 少于5%的慢更新
- };
- }
-
- #endregion
-
- #region 配置管理
-
- ///
- /// 设置监控配置
- ///
- /// 报告生成间隔
- /// 最大事件历史记录数
- /// 性能阈值(毫秒)
- public void ConfigureMonitoring(TimeSpan reportInterval, int maxEventHistory, int performanceThreshold)
- {
- _reportInterval = reportInterval;
- _maxEventHistory = Math.Max(100, maxEventHistory);
- _performanceThresholdMs = Math.Max(1, performanceThreshold);
-
- // 重启定时器
- _reportTimer?.Change(_reportInterval, _reportInterval);
-
- LogManager.Info($"数据绑定监控配置已更新 - 报告间隔: {reportInterval}, 事件历史: {maxEventHistory}, 性能阈值: {performanceThreshold}ms");
- }
-
- ///
- /// 重置所有监控数据
- ///
- public void ResetMonitoringData()
- {
- _propertyMetrics.Clear();
- _collectionMetrics.Clear();
-
- while (_updateEvents.TryDequeue(out _)) { }
-
- LogManager.Info("数据绑定监控数据已重置");
- }
-
- #endregion
-
- #region IDisposable实现
-
- public void Dispose()
- {
- if (_isDisposed) return;
-
- _isDisposed = true;
- _isMonitoringEnabled = false;
-
- _reportTimer?.Dispose();
-
- // 生成最终报告
- try
- {
- var finalReport = GenerateDetailedReport();
- LogManager.Info($"数据绑定性能监控最终报告:\n{finalReport}");
- }
- catch (Exception ex)
- {
- LogManager.Error($"生成最终性能报告失败: {ex.Message}");
- }
-
- _propertyMetrics.Clear();
- _collectionMetrics.Clear();
-
- LogManager.Info("数据绑定性能监控器已释放");
- }
-
- #endregion
- }
-
- #region 辅助类和数据结构
-
- ///
- /// 属性更新上下文 - 用于测量属性更新性能
- ///
- internal class PropertyUpdateContext : IDisposable
- {
- private readonly DataBindingPerformanceMonitor _monitor;
- private readonly string _key;
- private readonly Stopwatch _stopwatch;
- private readonly Dispatcher _dispatcher;
-
- public PropertyUpdateContext(DataBindingPerformanceMonitor monitor, string key, Stopwatch stopwatch, Dispatcher dispatcher)
- {
- _monitor = monitor;
- _key = key;
- _stopwatch = stopwatch;
- _dispatcher = dispatcher;
- }
-
- public void Dispose()
- {
- _monitor.EndPropertyUpdate(_key, _stopwatch, _dispatcher);
- }
- }
-
- ///
- /// 集合更新上下文 - 用于测量集合更新性能
- ///
- internal class CollectionUpdateContext : IDisposable
- {
- private readonly DataBindingPerformanceMonitor _monitor;
- private readonly string _key;
- private readonly Stopwatch _stopwatch;
- private readonly int _itemCount;
- private readonly Dispatcher _dispatcher;
-
- public CollectionUpdateContext(DataBindingPerformanceMonitor monitor, string key, Stopwatch stopwatch, int itemCount, Dispatcher dispatcher)
- {
- _monitor = monitor;
- _key = key;
- _stopwatch = stopwatch;
- _itemCount = itemCount;
- _dispatcher = dispatcher;
- }
-
- public void Dispose()
- {
- _monitor.EndCollectionUpdate(_key, _stopwatch, _itemCount, _dispatcher);
- }
- }
-
- ///
- /// 空的IDisposable实现 - 用于禁用监控时的占位符
- ///
- internal class EmptyDisposable : IDisposable
- {
- public void Dispose() { }
- }
-
- ///
- /// 属性更新性能指标
- ///
- public class PropertyUpdateMetrics
- {
- public string PropertyKey { get; }
- public long UpdateCount { get; private set; }
- public double TotalUpdateTime { get; private set; }
- public double MaxUpdateTime { get; private set; }
- public long SlowUpdateCount { get; private set; }
- public long CrossThreadUpdateCount { get; private set; }
- public DateTime LastUpdateTime { get; private set; }
-
- public double AverageUpdateTime => UpdateCount > 0 ? TotalUpdateTime / UpdateCount : 0;
-
- private readonly List _updateTimes = new List();
-
- public PropertyUpdateMetrics(string propertyKey)
- {
- PropertyKey = propertyKey;
- LastUpdateTime = DateTime.UtcNow;
- }
-
- public void RecordUpdate(double durationMs, bool isOnUIThread)
- {
- UpdateCount++;
- TotalUpdateTime += durationMs;
- MaxUpdateTime = Math.Max(MaxUpdateTime, durationMs);
- LastUpdateTime = DateTime.UtcNow;
-
- if (durationMs > 16) // 60FPS阈值
- {
- SlowUpdateCount++;
- }
-
- if (!isOnUIThread)
- {
- CrossThreadUpdateCount++;
- }
-
- _updateTimes.Add(DateTime.UtcNow);
- }
-
- public void CleanupOldData(DateTime cutoffTime)
- {
- _updateTimes.RemoveAll(t => t < cutoffTime);
- }
- }
-
- ///
- /// 集合更新性能指标
- ///
- public class CollectionUpdateMetrics
- {
- public string CollectionKey { get; }
- public long UpdateCount { get; private set; }
- public double TotalUpdateTime { get; private set; }
- public double MaxUpdateTime { get; private set; }
- public long TotalItemsProcessed { get; private set; }
- public long SlowUpdateCount { get; private set; }
- public DateTime LastUpdateTime { get; private set; }
-
- public double AverageUpdateTime => UpdateCount > 0 ? TotalUpdateTime / UpdateCount : 0;
- public double AverageItemsPerUpdate => UpdateCount > 0 ? (double)TotalItemsProcessed / UpdateCount : 0;
-
- private readonly List _updateTimes = new List();
-
- public CollectionUpdateMetrics(string collectionKey)
- {
- CollectionKey = collectionKey;
- LastUpdateTime = DateTime.UtcNow;
- }
-
- public void RecordUpdate(double durationMs, int itemCount, bool isOnUIThread)
- {
- UpdateCount++;
- TotalUpdateTime += durationMs;
- MaxUpdateTime = Math.Max(MaxUpdateTime, durationMs);
- TotalItemsProcessed += itemCount;
- LastUpdateTime = DateTime.UtcNow;
-
- if (durationMs > 50) // 集合操作的更宽松阈值
- {
- SlowUpdateCount++;
- }
-
- _updateTimes.Add(DateTime.UtcNow);
- }
-
- public void CleanupOldData(DateTime cutoffTime)
- {
- _updateTimes.RemoveAll(t => t < cutoffTime);
- }
- }
-
- ///
- /// 绑定更新事件
- ///
- public class BindingUpdateEvent
- {
- public DateTime Timestamp { get; set; }
- public string BindingPath { get; set; }
- public string UpdateType { get; set; }
- public double DurationMs { get; set; }
- public bool IsSlowUpdate { get; set; }
- public int ThreadId { get; set; }
- }
-
- ///
- /// 性能概览数据
- ///
- public class PerformanceOverview
- {
- public long TotalPropertyUpdates { get; set; }
- public long TotalCollectionUpdates { get; set; }
- public long TotalSlowUpdates { get; set; }
- public double AveragePropertyUpdateTime { get; set; }
- public double AverageCollectionUpdateTime { get; set; }
- public int MonitoredPropertiesCount { get; set; }
- public int MonitoredCollectionsCount { get; set; }
- public bool IsHealthy { get; set; }
- }
-
- #endregion
-}
\ No newline at end of file
diff --git a/src/UI/WPF/Services/SmartDataBindingOptimizer.cs b/src/UI/WPF/Services/SmartDataBindingOptimizer.cs
index b927665..39287fe 100644
--- a/src/UI/WPF/Services/SmartDataBindingOptimizer.cs
+++ b/src/UI/WPF/Services/SmartDataBindingOptimizer.cs
@@ -35,9 +35,6 @@ namespace NavisworksTransport.UI.WPF.Services
private readonly Dictionary _conditionalConfigs;
private readonly object _conditionalConfigLock = new object();
- // 性能监控集成
- private readonly DataBindingPerformanceMonitor _performanceMonitor;
-
// 配置选项
private TimeSpan _defaultDelayInterval = TimeSpan.FromMilliseconds(100);
private TimeSpan _batchUpdateInterval = TimeSpan.FromMilliseconds(50);
@@ -102,8 +99,6 @@ namespace NavisworksTransport.UI.WPF.Services
_batchUpdates = new Dictionary
private readonly SmartDataBindingOptimizer _bindingOptimizer;
- ///
- /// 数据绑定性能监控器,监控绑定性能
- ///
- private readonly DataBindingPerformanceMonitor _performanceMonitor;
-
///
/// 正在更新的属性集合,用于防重入检测
///
@@ -52,11 +47,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
///
protected bool IsSmartOptimizationEnabled { get; set; } = true;
- ///
- /// 是否启用性能监控
- ///
- protected bool IsPerformanceMonitoringEnabled { get; set; } = true;
-
///
/// 主ViewModel引用,用于统一状态栏更新
///
@@ -73,7 +63,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
_uiStateManager = UIStateManager.Instance;
_bindingOptimizer = SmartDataBindingOptimizer.Instance;
- _performanceMonitor = DataBindingPerformanceMonitor.Instance;
}
#endregion
@@ -81,7 +70,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
#region 属性更新方法
///
- /// 触发属性变更通知(线程安全,防重入,集成性能监控)
+ /// 触发属性变更通知(线程安全,防重入)
///
/// 属性名称,自动获取调用者名称
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
@@ -91,13 +80,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return;
}
- // 性能监控
- IDisposable performanceContext = null;
- if (IsPerformanceMonitoringEnabled)
- {
- performanceContext = _performanceMonitor.BeginPropertyUpdate(this.GetType(), propertyName);
- }
-
try
{
// 防重入检查
@@ -109,7 +91,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Debug($"[ViewModel] 检测到属性重入更新,跳过: {propertyName}");
return;
}
-
+
// 标记属性为正在更新状态
_updatingProperties.Add(propertyName);
}
@@ -140,9 +122,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
_updatingProperties.Remove(propertyName);
}
-
- // 结束性能监控
- performanceContext?.Dispose();
}
}
@@ -510,37 +489,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
#endregion
- #region 性能监控和诊断
-
- ///
- /// 获取当前ViewModel的绑定性能统计
- ///
- /// 性能统计信息
- protected virtual string GetBindingPerformanceReport()
- {
- if (!IsPerformanceMonitoringEnabled)
- return "性能监控已禁用";
-
- try
- {
- return _performanceMonitor.GenerateDetailedReport();
- }
- catch (Exception ex)
- {
- LogManager.Error($"[ViewModel] 生成性能报告失败: {ex.Message}");
- return $"生成性能报告失败: {ex.Message}";
- }
- }
-
- ///
- /// 启用或禁用性能监控
- ///
- /// 是否启用
- protected void SetPerformanceMonitoring(bool enabled)
- {
- IsPerformanceMonitoringEnabled = enabled;
- _performanceMonitor.IsMonitoringEnabled = enabled;
- }
+ #region 诊断方法
///
/// 启用或禁用智能优化
diff --git a/src/UI/WPF/Views/CollisionReportDialog.xaml b/src/UI/WPF/Views/CollisionReportDialog.xaml
index 2c37a82..30220d7 100644
--- a/src/UI/WPF/Views/CollisionReportDialog.xaml
+++ b/src/UI/WPF/Views/CollisionReportDialog.xaml
@@ -264,7 +264,7 @@ NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Nav
-
+