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(); _conditionalConfigs = new Dictionary(); - _performanceMonitor = DataBindingPerformanceMonitor.Instance; - // 初始化延迟更新定时器 _delayedUpdateTimer = new Timer(ProcessDelayedUpdates, null, _defaultDelayInterval, _defaultDelayInterval); @@ -241,10 +236,7 @@ namespace NavisworksTransport.UI.WPF.Services if (target == null) return; - using (_performanceMonitor.BeginPropertyUpdate(target.GetType(), context.PropertyName)) - { - context.UpdateAction?.Invoke(); - } + context.UpdateAction?.Invoke(); LogManager.Debug($"执行延迟更新: {context.PropertyName}, 合并次数: {context.UpdateCount}"); } @@ -436,22 +428,19 @@ namespace NavisworksTransport.UI.WPF.Services { var duration = DateTime.UtcNow - context.StartTime; - using (_performanceMonitor.BeginPropertyUpdate(target.GetType(), $"BatchUpdate_{properties.Count}Properties")) + // 如果目标实现了批量更新接口,使用其批量方法 + if (target is ViewModelBase viewModelBase) { - // 如果目标实现了批量更新接口,使用其批量方法 - if (target is ViewModelBase viewModelBase) + viewModelBase.OnPropertiesChanged(properties.ToArray()); + } + else + { + // 否则逐个触发属性变更通知 + foreach (var propertyName in properties) { - viewModelBase.OnPropertiesChanged(properties.ToArray()); - } - else - { - // 否则逐个触发属性变更通知 - foreach (var propertyName in properties) - { - // 使用反射或其他方式触发PropertyChanged事件 - // 这里需要根据具体的INotifyPropertyChanged实现来调用 - TriggerPropertyChanged(target, propertyName); - } + // 使用反射或其他方式触发PropertyChanged事件 + // 这里需要根据具体的INotifyPropertyChanged实现来调用 + TriggerPropertyChanged(target, propertyName); } } @@ -606,10 +595,7 @@ namespace NavisworksTransport.UI.WPF.Services try { // 执行延迟的更新 - using (_performanceMonitor.BeginPropertyUpdate(target.GetType(), propertyName)) - { - TriggerPropertyChanged(target, propertyName); - } + TriggerPropertyChanged(target, propertyName); lock (_conditionalConfigLock) { @@ -680,16 +666,6 @@ namespace NavisworksTransport.UI.WPF.Services report.AppendLine("条件更新机制:"); report.AppendLine($" 配置的条件更新: {stats.ConfiguredConditionalUpdates}"); - report.AppendLine(); - - // 集成性能监控数据 - var performanceOverview = _performanceMonitor.GetPerformanceOverview(); - report.AppendLine("性能概览:"); - report.AppendLine($" 总属性更新: {performanceOverview.TotalPropertyUpdates}"); - report.AppendLine($" 总集合更新: {performanceOverview.TotalCollectionUpdates}"); - report.AppendLine($" 慢更新次数: {performanceOverview.TotalSlowUpdates}"); - report.AppendLine($" 平均属性更新时间: {performanceOverview.AveragePropertyUpdateTime:F2}ms"); - report.AppendLine($" 系统健康状态: {(performanceOverview.IsHealthy ? "良好" : "需要优化")}"); return report.ToString(); } diff --git a/src/UI/WPF/ViewModels/ViewModelBase.cs b/src/UI/WPF/ViewModels/ViewModelBase.cs index 71c5bbf..2cf7ee0 100644 --- a/src/UI/WPF/ViewModels/ViewModelBase.cs +++ b/src/UI/WPF/ViewModels/ViewModelBase.cs @@ -32,11 +32,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// 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 - +