From ee1b0cbe3208ad72baf403e81c04a431f6ed9ea1 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Thu, 8 Jan 2026 19:50:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=99=9A=E6=8B=9F=E8=BD=A6?= =?UTF-8?q?=E8=BE=86=E5=B0=BA=E5=AF=B8=E5=8F=82=E6=95=B0=E5=88=B0=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=8A=A8=E7=94=BB=E7=AE=A1=E7=90=86=E5=99=A8=E5=92=8C?= =?UTF-8?q?=E7=A2=B0=E6=92=9E=E6=A3=80=E6=B5=8B=E7=BB=93=E6=9E=9C=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E5=BA=93=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Core/Animation/PathAnimationManager.cs | 14 +- .../Collision/ClashDetectiveIntegration.cs | 199 +++++++++++++++++- src/Core/PathDatabase.cs | 22 +- .../ViewModels/AnimationControlViewModel.cs | 10 +- src/Utils/NavisworksApiHelper.cs | 73 +++++++ 5 files changed, 308 insertions(+), 10 deletions(-) diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index efa6aaa..9621c64 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -90,6 +90,9 @@ namespace NavisworksTransport.Core.Animation private static readonly Dictionary> _collisionResultCache = new Dictionary>(); // 碰撞结果缓存 private ModelItem _animatedObject; private bool _isVirtualVehicle = false; // 是否使用虚拟车辆 + private double _virtualVehicleLength = 0; // 虚拟车辆长度(米) + private double _virtualVehicleWidth = 0; // 虚拟车辆宽度(米) + private double _virtualVehicleHeight = 0; // 虚拟车辆高度(米) private List _pathPoints; private List _manualCollisionTargets = new List(); private bool _manualCollisionOverrideEnabled = false; @@ -1174,7 +1177,10 @@ namespace NavisworksTransport.Core.Animation _animatedObject, _isVirtualVehicle, _animationFrameRate, - _animationDuration + _animationDuration, + _virtualVehicleLength, + _virtualVehicleWidth, + _virtualVehicleHeight ); _completedCollisionTests.Add(_currentAnimationHash); // 记录此配置已完成碰撞检测 LogManager.Info($"碰撞测试汇总已创建并记录,此配置后续播放将跳过碰撞检测"); @@ -2228,11 +2234,15 @@ namespace NavisworksTransport.Core.Animation /// 动画持续时间(秒) /// 路径名称 /// 路由ID - public void CreateAnimation(ModelItem animatedObject, List pathPoints, double durationSeconds = 10.0, string pathName = "未知路径", string routeId = null, bool isVirtualVehicle = false) + public void CreateAnimation(ModelItem animatedObject, List pathPoints, double durationSeconds = 10.0, string pathName = "未知路径", string routeId = null, bool isVirtualVehicle = false, + double virtualVehicleLength = 0, double virtualVehicleWidth = 0, double virtualVehicleHeight = 0) { _pathName = pathName; _currentRouteId = routeId; _isVirtualVehicle = isVirtualVehicle; // 设置是否使用虚拟车辆 + _virtualVehicleLength = virtualVehicleLength; + _virtualVehicleWidth = virtualVehicleWidth; + _virtualVehicleHeight = virtualVehicleHeight; SetupAnimation(animatedObject, durationSeconds, _route); SetState(AnimationState.Ready); diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs index a2b059a..b60c325 100644 --- a/src/Core/Collision/ClashDetectiveIntegration.cs +++ b/src/Core/Collision/ClashDetectiveIntegration.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using Autodesk.Navisworks.Api; using Autodesk.Navisworks.Api.Clash; +using NavisworksTransport.Core; +using NavisworksTransport.Core.Config; using NavisworksTransport.Utils; namespace NavisworksTransport @@ -165,7 +167,8 @@ namespace NavisworksTransport /// private void SaveClashDetectiveResultToDatabase(string pathName, string routeId, List clashResults, int frameRate, double duration, double detectionGap, - ModelItem animatedObject, bool isVirtualVehicle) + ModelItem animatedObject, bool isVirtualVehicle, + double virtualVehicleLength, double virtualVehicleWidth, double virtualVehicleHeight) { try { @@ -174,6 +177,7 @@ namespace NavisworksTransport { // 获取动画对象名称 string animatedObjectName = "未知对象"; + if (!isVirtualVehicle && animatedObject != null) { animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject); @@ -196,6 +200,9 @@ namespace NavisworksTransport } } + // 打印虚拟车辆尺寸(用于调试) + LogManager.Info($"[SaveClashDetectiveResultToDatabase] IsVirtualVehicle={isVirtualVehicle}, 虚拟车辆尺寸: Length={virtualVehicleLength:F2}m, Width={virtualVehicleWidth:F2}m, Height={virtualVehicleHeight:F2}m"); + var record = new ClashDetectiveResultRecord { TestName = _currentTestName, @@ -210,6 +217,9 @@ namespace NavisworksTransport AnimatedObjectName = animatedObjectName, IsVirtualVehicle = isVirtualVehicle, VehicleModelItemPath = vehicleModelItemPath, + VirtualVehicleLength = virtualVehicleLength, + VirtualVehicleWidth = virtualVehicleWidth, + VirtualVehicleHeight = virtualVehicleHeight, CreatedAt = DateTime.Now }; var resultId = pathDatabase.SaveClashDetectiveResult(record); @@ -254,6 +264,169 @@ namespace NavisworksTransport } } + /// + /// 从数据库加载ClashDetective碰撞结果 + /// + /// 测试名称 + /// 碰撞结果列表,如果加载失败返回null + private List LoadClashDetectiveResultsFromDatabase(string testName) + { + try + { + var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase(); + if (pathDatabase == null) + { + LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] PathDatabase不可用"); + return null; + } + + // 1. 从数据库读取测试信息 + var testInfoSql = @" + SELECT Id, PathName, RouteId, IsVirtualVehicle, VehicleModelItemPath, + VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight + FROM ClashDetectiveResults + WHERE TestName = @testName + "; + + ClashDetectiveResultRecord testInfo = null; + using (var cmd = new System.Data.SQLite.SQLiteCommand(testInfoSql, pathDatabase._connection)) + { + cmd.Parameters.AddWithValue("@testName", testName); + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + testInfo = new ClashDetectiveResultRecord + { + Id = Convert.ToInt32(reader["Id"]), + PathName = reader["PathName"].ToString(), + RouteId = reader["RouteId"]?.ToString(), + IsVirtualVehicle = Convert.ToBoolean(reader["IsVirtualVehicle"]), + VehicleModelItemPath = reader["VehicleModelItemPath"]?.ToString(), + VirtualVehicleLength = reader["VirtualVehicleLength"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleLength"]) : 0.0, + VirtualVehicleWidth = reader["VirtualVehicleWidth"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleWidth"]) : 0.0, + VirtualVehicleHeight = reader["VirtualVehicleHeight"] != DBNull.Value ? Convert.ToDouble(reader["VirtualVehicleHeight"]) : 0.0 + }; + } + } + } + + if (testInfo == null) + { + LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 未找到测试记录: {testName}"); + return null; + } + + // 2. 重建车辆对象 + ModelItem vehicleObject = null; + if (testInfo.IsVirtualVehicle) + { + // 创建虚拟车辆 + vehicleObject = VirtualVehicleManager.Instance.CreateVirtualVehicle( + testInfo.VirtualVehicleLength, + testInfo.VirtualVehicleWidth, + testInfo.VirtualVehicleHeight + ); + if (vehicleObject == null) + { + LogManager.Error($"[LoadClashDetectiveResultsFromDatabase] 创建虚拟车辆失败"); + return null; + } + LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已创建虚拟车辆: {testInfo.VirtualVehicleLength}×{testInfo.VirtualVehicleWidth}×{testInfo.VirtualVehicleHeight}m"); + } + else if (!string.IsNullOrEmpty(testInfo.VehicleModelItemPath)) + { + // 通过路径查找真实车辆 + var paths = testInfo.VehicleModelItemPath.Split(';'); + vehicleObject = NavisworksApiHelper.FindModelItemByPath(paths[0]); + if (vehicleObject == null) + { + LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 无法通过路径找到车辆对象: {paths[0]}"); + return null; + } + LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已找到真实车辆: {ModelItemAnalysisHelper.GetSafeDisplayName(vehicleObject)}"); + } + + if (vehicleObject == null) + { + LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 无法重建车辆对象"); + return null; + } + + // 3. 从数据库读取被撞物体信息 + var collisionObjectsSql = @" + SELECT ModelItemPath, DisplayName, ObjectName + FROM ClashDetectiveCollisionObjects + WHERE ResultId = @resultId + "; + + var collisionObjects = new List(); + using (var cmd = new System.Data.SQLite.SQLiteCommand(collisionObjectsSql, pathDatabase._connection)) + { + cmd.Parameters.AddWithValue("@resultId", testInfo.Id); + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + collisionObjects.Add(new ClashDetectiveCollisionObjectRecord + { + ModelItemPath = reader["ModelItemPath"]?.ToString(), + DisplayName = reader["DisplayName"]?.ToString(), + ObjectName = reader["ObjectName"]?.ToString() + }); + } + } + } + + // 4. 重建碰撞结果 + var results = new List(); + var processedPaths = new HashSet(); + + foreach (var obj in collisionObjects) + { + // 通过路径查找被撞物体 + var collidedObject = NavisworksApiHelper.FindModelItemByPath(obj.ModelItemPath); + if (collidedObject != null && ModelItemAnalysisHelper.IsModelItemValid(collidedObject)) + { + // 避免重复添加(同一物体可能被撞多次) + if (!processedPaths.Contains(obj.ModelItemPath)) + { + processedPaths.Add(obj.ModelItemPath); + + var collisionResult = new CollisionResult + { + ClashGuid = Guid.NewGuid(), + DisplayName = $"历史碰撞: {obj.DisplayName}", + Status = ClashResultStatus.Active, + Item1 = vehicleObject, + Item2 = collidedObject, + Center = collidedObject.BoundingBox().Center, + Distance = 0.0, + CreatedTime = DateTime.Now, + OriginalItem1 = vehicleObject, + OriginalItem2 = collidedObject, + HasContainerMapping = true + }; + + results.Add(collisionResult); + } + } + else + { + LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 无法通过路径找到被撞物体: {obj.ModelItemPath}"); + } + } + + LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 从数据库加载测试 '{testName}' 完成,重建了 {results.Count} 个碰撞结果"); + return results; + } + catch (Exception ex) + { + LogManager.Error($"[LoadClashDetectiveResultsFromDatabase] 加载失败: {ex.Message}", ex); + return null; + } + } + /// /// 合并复合对象碰撞结果 /// 将几何体级别的碰撞结果合并成复合对象级别的碰撞结果(去重) @@ -411,10 +584,14 @@ namespace NavisworksTransport /// 是否使用虚拟车辆 /// 帧率 /// 动画时长 + /// 虚拟车辆长度(米) + /// 虚拟车辆宽度(米) + /// 虚拟车辆高度(米) public void CreateAllAnimationCollisionTests(List precomputedCollisions, double detectionGap = 0.05, string pathName = "未知路径", string routeId = null, ModelItem animatedObject = null, bool isVirtualVehicle = false, - int frameRate = 30, double duration = 10.0) + int frameRate = 30, double duration = 10.0, + double virtualVehicleLength = 0, double virtualVehicleWidth = 0, double virtualVehicleHeight = 0) { try { @@ -703,7 +880,8 @@ namespace NavisworksTransport _clashDetectiveCollisionCount = clashResults.Count; // 保存到数据库 - SaveClashDetectiveResultToDatabase(pathName, routeId, clashResults, frameRate, duration, detectionGap, animatedObject, isVirtualVehicle); + SaveClashDetectiveResultToDatabase(pathName, routeId, clashResults, frameRate, duration, detectionGap, animatedObject, isVirtualVehicle, + virtualVehicleLength, virtualVehicleWidth, virtualVehicleHeight); // 第四步:将分组添加到主测试 if (collisionGroup.Children.Count > 0) @@ -1077,14 +1255,27 @@ namespace NavisworksTransport { lock (_clashResultsCacheLock) { + // 先尝试从缓存获取 if (_clashDetectiveResultsCache.TryGetValue(testName, out var cachedResults)) { LogManager.Debug($"[ClashDetective结果] 从缓存获取 '{testName}' 的结果:{cachedResults.Count}个碰撞"); return cachedResults; } + + // 缓存中没有,尝试从数据库加载 + LogManager.Info($"[ClashDetective结果] 缓存中没有 '{testName}',尝试从数据库加载"); + var loadedResults = LoadClashDetectiveResultsFromDatabase(testName); + + if (loadedResults != null && loadedResults.Count > 0) + { + // 缓存加载的结果 + _clashDetectiveResultsCache[testName] = loadedResults; + LogManager.Info($"[ClashDetective结果] 已从数据库加载 '{testName}' 的结果并缓存:{loadedResults.Count}个碰撞"); + return loadedResults; + } } - throw new InvalidOperationException($"未找到测试 '{testName}' 的ClashDetective缓存结果,请先运行碰撞检测"); + throw new InvalidOperationException($"未找到测试 '{testName}' 的ClashDetective结果(缓存和数据库中都没有)"); } /// diff --git a/src/Core/PathDatabase.cs b/src/Core/PathDatabase.cs index f7ba630..01f68ee 100644 --- a/src/Core/PathDatabase.cs +++ b/src/Core/PathDatabase.cs @@ -14,9 +14,14 @@ namespace NavisworksTransport /// public class PathDatabase : IDisposable { - private SQLiteConnection _connection; + internal SQLiteConnection _connection; private readonly string _dbPath; + /// + /// 数据库连接(供内部使用) + /// + internal SQLiteConnection Connection => _connection; + /// /// 初始化路径数据库 /// @@ -172,6 +177,9 @@ namespace NavisworksTransport AnimatedObjectName TEXT, IsVirtualVehicle INTEGER, VehicleModelItemPath TEXT, + VirtualVehicleLength REAL, + VirtualVehicleWidth REAL, + VirtualVehicleHeight REAL, CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ) "); @@ -406,9 +414,11 @@ namespace NavisworksTransport var sql = @" INSERT INTO ClashDetectiveResults (TestName, PathName, RouteId, TestTime, CollisionCount, AnimationCollisionCount, - FrameRate, Duration, DetectionGap, AnimatedObjectName, IsVirtualVehicle, VehicleModelItemPath, CreatedAt) + FrameRate, Duration, DetectionGap, AnimatedObjectName, IsVirtualVehicle, VehicleModelItemPath, + VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight, CreatedAt) VALUES (@testName, @pathName, @routeId, @testTime, @collisionCount, @animationCollisionCount, - @frameRate, @duration, @detectionGap, @animatedObjectName, @isVirtualVehicle, @vehiclePath, @createdAt) + @frameRate, @duration, @detectionGap, @animatedObjectName, @isVirtualVehicle, @vehiclePath, + @virtualVehicleLength, @virtualVehicleWidth, @virtualVehicleHeight, @createdAt) "; long newId = 0; @@ -426,6 +436,9 @@ namespace NavisworksTransport cmd.Parameters.AddWithValue("@animatedObjectName", record.AnimatedObjectName ?? ""); cmd.Parameters.AddWithValue("@isVirtualVehicle", record.IsVirtualVehicle); cmd.Parameters.AddWithValue("@vehiclePath", record.VehicleModelItemPath ?? ""); + cmd.Parameters.AddWithValue("@virtualVehicleLength", record.VirtualVehicleLength); + cmd.Parameters.AddWithValue("@virtualVehicleWidth", record.VirtualVehicleWidth); + cmd.Parameters.AddWithValue("@virtualVehicleHeight", record.VirtualVehicleHeight); cmd.Parameters.AddWithValue("@createdAt", record.CreatedAt); cmd.ExecuteNonQuery(); newId = _connection.LastInsertRowId; @@ -1198,6 +1211,9 @@ namespace NavisworksTransport public string AnimatedObjectName { get; set; } public bool IsVirtualVehicle { get; set; } public string VehicleModelItemPath { get; set; } + public double VirtualVehicleLength { get; set; } + public double VirtualVehicleWidth { get; set; } + public double VirtualVehicleHeight { get; set; } public DateTime CreatedAt { get; set; } } diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index 8c49102..6b87139 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -2187,7 +2187,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 先设置路径到动画管理器 _pathAnimationManager.SetRoute(pathRoute); - _pathAnimationManager.CreateAnimation(animatedObject, pathPoints, AnimationDuration, CurrentPathRoute.Name, CurrentPathRoute.Id, UseVirtualVehicle); + // 准备车辆尺寸参数 + double vLength = UseVirtualVehicle ? VirtualVehicleLength : 0; + double vWidth = UseVirtualVehicle ? VirtualVehicleWidth : 0; + double vHeight = UseVirtualVehicle ? VirtualVehicleHeight : 0; + + LogManager.Info($"[ExecuteGenerateAnimation] 准备调用CreateAnimation: UseVirtualVehicle={UseVirtualVehicle}, 车辆尺寸: {vLength:F2}×{vWidth:F2}×{vHeight:F2}m"); + + _pathAnimationManager.CreateAnimation(animatedObject, pathPoints, AnimationDuration, CurrentPathRoute.Name, CurrentPathRoute.Id, UseVirtualVehicle, + vLength, vWidth, vHeight); var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds; LogManager.Info($"[动画生成] 动画生成完成,总耗时: {totalElapsed:F1}ms"); diff --git a/src/Utils/NavisworksApiHelper.cs b/src/Utils/NavisworksApiHelper.cs index 2da6158..688fc06 100644 --- a/src/Utils/NavisworksApiHelper.cs +++ b/src/Utils/NavisworksApiHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Autodesk.Navisworks.Api; namespace NavisworksTransport.Utils @@ -130,5 +131,77 @@ namespace NavisworksTransport.Utils return "获取失败"; } } + + /// + /// 通过索引路径查找ModelItem + /// 路径格式:逗号分隔的索引数组,如"1,3,5" + /// 注意:COM API的路径是1-based索引,需要转换为0-based + /// + /// 路径字符串 + /// 找到的ModelItem,如果找不到返回null + public static ModelItem FindModelItemByPath(string pathString) + { + if (string.IsNullOrEmpty(pathString)) + return null; + + try + { + // 解析路径数组(1-based索引) + var pathParts = pathString.Split(',') + .Select(p => p.Trim()) + .Where(p => !string.IsNullOrEmpty(p)) + .Select(p => int.Parse(p)) + .ToArray(); + + if (pathParts.Length == 0) + { + LogManager.Warning($"[FindModelItemByPath] 路径 '{pathString}' 解析失败"); + return null; + } + + // 从RootItems开始查找 + var rootItems = Application.ActiveDocument.Models.RootItems; + var firstRoot = rootItems.FirstOrDefault(); + + if (firstRoot == null) + { + LogManager.Warning("[FindModelItemByPath] 文档根节点为空"); + return null; + } + + // 递归查找 + var currentItem = firstRoot; + for (int i = 0; i < pathParts.Length; i++) + { + // COM API是1-based索引,转换为0-based + var targetIndex = pathParts[i] - 1; + var children = currentItem.Children; + + if (children == null || children.Count() <= targetIndex) + { + LogManager.Warning($"[FindModelItemByPath] 索引超出范围: 索引={targetIndex}, 子节点数={children?.Count() ?? 0}"); + return null; + } + + currentItem = children.ElementAt(targetIndex); + } + + if (currentItem != null) + { + LogManager.Debug($"[FindModelItemByPath] 成功找到ModelItem: {currentItem.DisplayName}"); + } + else + { + LogManager.Warning($"[FindModelItemByPath] 未找到ModelItem: {pathString}"); + } + + return currentItem; + } + catch (Exception ex) + { + LogManager.Error($"[FindModelItemByPath] 查找失败: {ex.Message}", ex); + return null; + } + } } } \ No newline at end of file