diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 110cc46..c02678d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -155,7 +155,9 @@ "Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" AStarTestRunner.csproj)", "Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" NavisworksTransport.UnitTests.csproj)", "Bash(\"bin\\Debug\\AStarTestRunner.exe\")", - "Bash(git restore:*)" + "Bash(git restore:*)", + "Read(//c/Program Files/Autodesk/Navisworks Manage 2026/**)", + "Bash(where:*)" ], "deny": [], "additionalDirectories": [ diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 8de5f9b..25b291c 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -91,7 +91,13 @@ packages\RoyT.AStar.3.0.2\lib\netstandard2.0\Roy-T.AStar.dll True - + + + + packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.118.0\lib\net46\System.Data.SQLite.dll + True + + @@ -106,6 +112,8 @@ + + diff --git a/clean_database.bat b/clean_database.bat new file mode 100644 index 0000000..3866d43 --- /dev/null +++ b/clean_database.bat @@ -0,0 +1,35 @@ +@echo off +echo ======================================== +echo NavisworksTransport 数据库清理工具 +echo ======================================== +echo. +echo 此脚本将删除所有现有的路径数据库文件(.db文件) +echo 下次启动插件时将自动创建新的数据库结构 +echo. +echo 警告:此操作将删除所有历史路径数据! +echo. +pause + +REM 设置数据库文件所在目录 +set DB_DIR=C:\Users\Tellme\Documents\NavisworksTransport\分层输出 + +echo. +echo 正在清理数据库文件... +echo 目录: %DB_DIR% +echo. + +REM 删除所有.db文件 +if exist "%DB_DIR%\*.db" ( + del /f /q "%DB_DIR%\*.db" + echo 已删除所有数据库文件 +) else ( + echo 未找到数据库文件 +) + +echo. +echo ======================================== +echo 数据库清理完成! +echo 下次启动Navisworks插件时将自动创建新的数据库 +echo ======================================== +echo. +pause \ No newline at end of file diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index b5e0aa1..2f7e32d 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -2,6 +2,10 @@ ## 功能点 +### [2025/10/01] + +1. [x] (功能)路径分析 + ### [2025/09/29] 1. [x] (功能)导出导航地图为图片 diff --git a/packages.config b/packages.config index 5eb4173..0d232e8 100644 --- a/packages.config +++ b/packages.config @@ -4,4 +4,5 @@ + \ No newline at end of file diff --git a/src/Core/MainPlugin.cs b/src/Core/MainPlugin.cs index 4fa6014..2256d1e 100644 --- a/src/Core/MainPlugin.cs +++ b/src/Core/MainPlugin.cs @@ -471,10 +471,24 @@ namespace NavisworksTransport LogManager.Info($"[文档管理] 文档文件名: {activeDoc?.FileName ?? "空"}"); LogManager.Info($"[文档管理] 文档标题: {activeDoc?.Title ?? "空"}"); - // 分析状态变化类型 + // 分析状态变化类型并执行相应操作 if (!_hasModels && currentHasModels) { LogManager.Info($"[文档管理] *** 状态分析: 从无到有(可能是打开文档)***"); + + // 文档加载完成,初始化PathDatabase + if (!string.IsNullOrEmpty(activeDoc?.FileName)) + { + GlobalExceptionHandler.SafeExecute(() => + { + var pathManager = PathPlanningManager.GetActivePathManager(); + if (pathManager != null) + { + pathManager.EnsureDatabaseInitialized(); + LogManager.Info("[文档管理] 已触发PathDatabase初始化"); + } + }, "初始化PathDatabase"); + } } else if (_hasModels && !currentHasModels) { @@ -488,14 +502,11 @@ namespace NavisworksTransport { LogManager.Info($"[文档管理] *** 状态分析: 保持有模型状态(模型数量变化)***"); } - + LogManager.Info("========================================"); - + // 更新状态 _hasModels = currentHasModels; - - // 暂时不执行任何清理或初始化操作 - LogManager.Info("[文档管理] 观察模式:不执行清理或初始化操作"); } /// diff --git a/src/Core/PathAnalysisService.cs b/src/Core/PathAnalysisService.cs new file mode 100644 index 0000000..1d981ff --- /dev/null +++ b/src/Core/PathAnalysisService.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NavisworksTransport.Utils; + +namespace NavisworksTransport +{ + /// + /// 路径分析服务 - 简化版 + /// 负责路径碰撞分析和评分计算 + /// + public class PathAnalysisService + { + private readonly PathDatabase _database; + + public PathAnalysisService(PathDatabase database) + { + _database = database ?? throw new ArgumentNullException(nameof(database)); + } + + /// + /// 分析单条路径 + /// + /// 路径对象 + /// 分析策略:安全优先/效率优先 + /// 分析结果 + public PathAnalysisResult AnalyzePath(PathRoute route, string strategy = "安全优先") + { + try + { + // 1. 执行碰撞检测 + var collisions = DetectCollisions(route); + + // 2. 保存碰撞结果到数据库 + _database.SaveCollisions(route.Id, collisions); + + // 3. 计算评分 + var safetyScore = CalculateSafetyScore(collisions.Count, route.TotalLength); + var efficiencyScore = CalculateEfficiencyScore(route.TotalLength, route.EstimatedTime); + + // 4. 保存分析结果到数据库 + _database.SaveAnalysisScore(route.Id, collisions.Count, safetyScore, efficiencyScore, strategy); + + LogManager.Info($"路径分析完成: {route.Name}, 碰撞={collisions.Count}, 安全分={safetyScore:F1}, 效率分={efficiencyScore:F1}"); + + return _database.GetAnalysisResult(route.Id); + } + catch (Exception ex) + { + LogManager.Error($"路径分析失败: {route?.Name ?? "未知路径"}, {ex.Message}", ex); + throw; + } + } + + /// + /// 对比多条路径,返回最佳路径和建议 + /// + /// 路径列表 + /// 分析策略 + /// 对比结果 + public PathComparisonResult CompareRoutes(List routes, string strategy = "安全优先") + { + if (routes == null || routes.Count == 0) + throw new ArgumentException("路径列表不能为空"); + + var result = new PathComparisonResult + { + Strategy = strategy, + Analyses = new List() + }; + + // 1. 分析每条路径 + foreach (var route in routes) + { + var analysis = AnalyzePath(route, strategy); + result.Analyses.Add(analysis); + } + + // 2. 根据策略选择最佳路径 + PathAnalysisResult bestRoute; + if (strategy == "安全优先") + { + // 优先选择碰撞少、安全分高的路径 + bestRoute = result.Analyses + .OrderBy(a => a.CollisionCount) + .ThenByDescending(a => a.SafetyScore) + .First(); + } + else // 效率优先 + { + // 优先选择效率分高、长度短的路径 + bestRoute = result.Analyses + .OrderByDescending(a => a.EfficiencyScore) + .ThenBy(a => a.CollisionCount) + .First(); + } + + result.BestRouteId = bestRoute.RouteId; + result.BestRouteName = routes.First(r => r.Id == bestRoute.RouteId).Name; + + // 3. 生成优化建议 + result.Suggestions = GenerateSuggestions(result.Analyses, routes, strategy); + + LogManager.Info($"路径对比完成: 共{routes.Count}条路径, 最佳路径={result.BestRouteName}, 策略={strategy}"); + + return result; + } + + /// + /// 执行碰撞检测 + /// 注意:实际的碰撞检测需要在路径动画执行时进行 + /// 这里优先从数据库加载已有的碰撞结果 + /// + private List DetectCollisions(PathRoute route) + { + try + { + // 先尝试从数据库获取已有的碰撞数据 + var existingCount = _database.GetCollisionCount(route.Id); + + if (existingCount > 0) + { + LogManager.Info($"从数据库加载碰撞数据: {route.Name}, 碰撞数={existingCount}"); + // 返回空列表,碰撞数量已经从数据库获取 + return new List(); + } + + // 如果数据库没有碰撞数据,返回空列表 + // TODO: 未来可以在这里触发实际的碰撞检测 + LogManager.Info($"路径{route.Name}暂无碰撞数据"); + return new List(); + } + catch (Exception ex) + { + LogManager.Error($"获取碰撞数据失败: {route.Name}, {ex.Message}", ex); + return new List(); + } + } + + /// + /// 计算安全评分(100分制) + /// 碰撞越少分数越高 + /// + private double CalculateSafetyScore(int collisionCount, double pathLength) + { + // 基础分:100分 + double baseScore = 100.0; + + // 每次碰撞扣10分 + double collisionPenalty = collisionCount * 10.0; + + // 长路径额外扣分(每100米扣1分) + double lengthPenalty = (pathLength / 100.0) * 0.5; + + double score = baseScore - collisionPenalty - lengthPenalty; + + // 分数不低于0 + return Math.Max(0, score); + } + + /// + /// 计算效率评分(100分制) + /// 路径越短、速度越快分数越高 + /// + private double CalculateEfficiencyScore(double pathLength, double estimatedTime) + { + if (estimatedTime <= 0) + return 0; + + // 计算平均速度 (m/s) + double avgSpeed = pathLength / estimatedTime; + + // 速度分数:速度越快分数越高 + // 假设理想速度为1.0 m/s,速度每提升0.1则加10分 + double speedScore = Math.Min(100, avgSpeed * 100); + + // 路径长度惩罚:路径越短越好 + // 基准长度100米,每超过10米扣1分 + double lengthPenalty = Math.Max(0, (pathLength - 100) / 10); + + double score = speedScore - lengthPenalty; + + return Math.Max(0, Math.Min(100, score)); + } + + /// + /// 生成优化建议(基于简单规则) + /// + private List GenerateSuggestions(List analyses, + List routes, string strategy) + { + var suggestions = new List(); + + // 规则1:碰撞过多 + var maxCollision = analyses.Max(a => a.CollisionCount); + if (maxCollision > 5) + { + suggestions.Add($"✅ 优先处理碰撞热点,当前最多{maxCollision}次碰撞"); + } + else if (maxCollision > 0) + { + suggestions.Add($"⚠️ 存在{maxCollision}次碰撞,建议优化路径避让"); + } + + // 规则2:路径长度差异大 + var avgLength = routes.Average(r => r.TotalLength); + var longRoutes = routes.Where(r => r.TotalLength > avgLength * 1.3).ToList(); + if (longRoutes.Count > 0) + { + suggestions.Add($"✅ {longRoutes.Count}条路径偏长(超过平均值30%),建议优化路线"); + } + + // 规则3:时间效率建议 + var avgTime = routes.Average(r => r.EstimatedTime); + var slowRoutes = routes.Where(r => r.EstimatedTime > avgTime * 1.2).ToList(); + if (slowRoutes.Count > 0) + { + suggestions.Add($"⚠️ {slowRoutes.Count}条路径耗时较长,考虑提高通行速度或缩短路径"); + } + + // 规则4:零碰撞路径推荐 + var zeroCollisionRoutes = analyses.Where(a => a.CollisionCount == 0).ToList(); + if (zeroCollisionRoutes.Count > 0) + { + suggestions.Add($"✅ {zeroCollisionRoutes.Count}条路径无碰撞,优先推荐使用"); + } + + // 规则5:策略特定建议 + if (strategy == "安全优先") + { + suggestions.Add("💡 当前策略:安全优先 - 建议选择碰撞最少的路径"); + } + else + { + suggestions.Add("💡 当前策略:效率优先 - 建议选择最短路径,但需注意安全"); + } + + // 如果没有任何建议,添加默认建议 + if (suggestions.Count == 0) + { + suggestions.Add("✅ 所有路径表现良好,可根据实际需求选择"); + } + + return suggestions; + } + } + + /// + /// 路径对比结果 + /// + public class PathComparisonResult + { + /// 最佳路径ID + public string BestRouteId { get; set; } + + /// 最佳路径名称 + public string BestRouteName { get; set; } + + /// 分析策略 + public string Strategy { get; set; } + + /// 所有路径的分析结果 + public List Analyses { get; set; } + + /// 优化建议列表 + public List Suggestions { get; set; } + } +} diff --git a/src/Core/PathDatabase.cs b/src/Core/PathDatabase.cs new file mode 100644 index 0000000..366eda7 --- /dev/null +++ b/src/Core/PathDatabase.cs @@ -0,0 +1,519 @@ +using System; +using System.Collections.Generic; +using System.Data.SQLite; +using System.IO; +using Autodesk.Navisworks.Api; +using NavisworksTransport.Utils; + +namespace NavisworksTransport +{ + /// + /// 路径数据库管理器 - 简化版 + /// 负责路径和碰撞数据的持久化存储 + /// + public class PathDatabase : IDisposable + { + private SQLiteConnection _connection; + private readonly string _dbPath; + + /// + /// 初始化路径数据库 + /// + /// Navisworks文档路径 + public PathDatabase(string documentPath) + { + if (string.IsNullOrEmpty(documentPath)) + throw new ArgumentException("文档路径不能为空"); + + // 数据库文件与nwd文件同名,扩展名为.db + _dbPath = Path.ChangeExtension(documentPath, ".db"); + + InitializeDatabase(); + } + + /// + /// 初始化数据库连接和表结构 + /// + private void InitializeDatabase() + { + bool isNewDatabase = !File.Exists(_dbPath); + + _connection = new SQLiteConnection($"Data Source={_dbPath};Version=3;"); + _connection.Open(); + + // 启用WAL模式(Write-Ahead Logging,支持并发读) + ExecuteNonQuery("PRAGMA journal_mode=WAL"); + ExecuteNonQuery("PRAGMA foreign_keys=ON"); + + if (isNewDatabase) + { + CreateTables(); + LogManager.Info($"创建新数据库: {_dbPath}"); + } + else + { + LogManager.Info($"打开现有数据库: {_dbPath}"); + } + } + + /// + /// 创建数据库表结构 + /// + private void CreateTables() + { + // 1. 路径基本信息表 + ExecuteNonQuery(@" + CREATE TABLE IF NOT EXISTS PathRoutes ( + Id TEXT PRIMARY KEY, + Name TEXT NOT NULL, + TotalLength REAL, + EstimatedTime REAL, + CreatedTime DATETIME, + LastModified DATETIME + ) + "); + + // 2. 碰撞结果表 + ExecuteNonQuery(@" + CREATE TABLE IF NOT EXISTS CollisionResults ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + RouteId TEXT NOT NULL, + Item1Name TEXT, + Item2Name TEXT, + CenterX REAL, + CenterY REAL, + CenterZ REAL, + Distance REAL, + DetectedTime DATETIME, + FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE + ) + "); + + // 3. 分析结果表 + ExecuteNonQuery(@" + CREATE TABLE IF NOT EXISTS AnalysisResults ( + RouteId TEXT PRIMARY KEY, + CollisionCount INTEGER, + SafetyScore REAL, + EfficiencyScore REAL, + OverallScore REAL, + AnalysisTime DATETIME, + Strategy TEXT, + FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE + ) + "); + + // 4. 路径点表 + ExecuteNonQuery(@" + CREATE TABLE IF NOT EXISTS PathPoints ( + Id TEXT PRIMARY KEY, + RouteId TEXT NOT NULL, + SequenceNumber INTEGER NOT NULL, + Name TEXT, + X REAL, + Y REAL, + Z REAL, + Type INTEGER, + FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE + ) + "); + + // 创建索引 + ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_collisions_route ON CollisionResults(RouteId)"); + ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_pathpoints_route ON PathPoints(RouteId)"); + } + + /// + /// 保存路径基本信息 + /// + public void SavePathRoute(PathRoute route) + { + // 使用事务确保路径和路径点一起保存 + using (var transaction = _connection.BeginTransaction()) + { + try + { + // 保存路径基本信息 + var sql = @" + INSERT OR REPLACE INTO PathRoutes + (Id, Name, TotalLength, EstimatedTime, CreatedTime, LastModified) + VALUES (@id, @name, @length, @time, @created, @modified) + "; + + using (var cmd = new SQLiteCommand(sql, _connection)) + { + cmd.Parameters.AddWithValue("@id", route.Id); + cmd.Parameters.AddWithValue("@name", route.Name); + cmd.Parameters.AddWithValue("@length", route.TotalLength); + cmd.Parameters.AddWithValue("@time", route.EstimatedTime); + cmd.Parameters.AddWithValue("@created", route.CreatedTime); + cmd.Parameters.AddWithValue("@modified", DateTime.Now); + cmd.ExecuteNonQuery(); + } + + // 删除旧的路径点 + ExecuteNonQuery("DELETE FROM PathPoints WHERE RouteId=@id", new { id = route.Id }); + + // 保存新的路径点 + if (route.Points != null && route.Points.Count > 0) + { + var pointSql = @" + INSERT INTO PathPoints + (Id, RouteId, SequenceNumber, Name, X, Y, Z, Type) + VALUES (@id, @routeId, @seq, @name, @x, @y, @z, @type) + "; + + for (int i = 0; i < route.Points.Count; i++) + { + var point = route.Points[i]; + using (var cmd = new SQLiteCommand(pointSql, _connection)) + { + cmd.Parameters.AddWithValue("@id", point.Id); + cmd.Parameters.AddWithValue("@routeId", route.Id); + cmd.Parameters.AddWithValue("@seq", i); + cmd.Parameters.AddWithValue("@name", point.Name ?? ""); + cmd.Parameters.AddWithValue("@x", point.Position.X); + cmd.Parameters.AddWithValue("@y", point.Position.Y); + cmd.Parameters.AddWithValue("@z", point.Position.Z); + cmd.Parameters.AddWithValue("@type", (int)point.Type); + cmd.ExecuteNonQuery(); + } + } + + LogManager.Info($"保存路径: {route.Name},包含 {route.Points.Count} 个路径点"); + } + + transaction.Commit(); + } + catch (Exception ex) + { + transaction.Rollback(); + LogManager.Error($"保存路径失败: {ex.Message}", ex); + throw; + } + } + } + + /// + /// 保存路径的碰撞结果 + /// + public void SaveCollisions(string routeId, List collisions) + { + // 先删除旧的碰撞数据 + ExecuteNonQuery("DELETE FROM CollisionResults WHERE RouteId=@id", + new { id = routeId }); + + if (collisions == null || collisions.Count == 0) + return; + + // 批量插入新数据 + using (var transaction = _connection.BeginTransaction()) + { + try + { + var sql = @" + INSERT INTO CollisionResults + (RouteId, Item1Name, Item2Name, CenterX, CenterY, CenterZ, Distance, DetectedTime) + VALUES (@rid, @i1, @i2, @x, @y, @z, @dist, @time) + "; + + foreach (var collision in collisions) + { + using (var cmd = new SQLiteCommand(sql, _connection)) + { + cmd.Parameters.AddWithValue("@rid", routeId); + cmd.Parameters.AddWithValue("@i1", GetItemDisplayName(collision.Item1)); + cmd.Parameters.AddWithValue("@i2", GetItemDisplayName(collision.Item2)); + cmd.Parameters.AddWithValue("@x", collision.Center.X); + cmd.Parameters.AddWithValue("@y", collision.Center.Y); + cmd.Parameters.AddWithValue("@z", collision.Center.Z); + cmd.Parameters.AddWithValue("@dist", collision.Distance); + cmd.Parameters.AddWithValue("@time", DateTime.Now); + cmd.ExecuteNonQuery(); + } + } + + transaction.Commit(); + LogManager.Info($"保存碰撞结果: RouteId={routeId}, Count={collisions.Count}"); + } + catch (Exception ex) + { + transaction.Rollback(); + LogManager.Error($"保存碰撞结果失败: {ex.Message}", ex); + throw; + } + } + } + + /// + /// 保存分析评分结果 + /// + public void SaveAnalysisScore(string routeId, int collisionCount, double safetyScore, + double efficiencyScore, string strategy) + { + var overallScore = (safetyScore + efficiencyScore) / 2.0; + + var sql = @" + INSERT OR REPLACE INTO AnalysisResults + (RouteId, CollisionCount, SafetyScore, EfficiencyScore, OverallScore, AnalysisTime, Strategy) + VALUES (@id, @count, @safety, @eff, @overall, @time, @strategy) + "; + + using (var cmd = new SQLiteCommand(sql, _connection)) + { + cmd.Parameters.AddWithValue("@id", routeId); + cmd.Parameters.AddWithValue("@count", collisionCount); + cmd.Parameters.AddWithValue("@safety", safetyScore); + cmd.Parameters.AddWithValue("@eff", efficiencyScore); + cmd.Parameters.AddWithValue("@overall", overallScore); + cmd.Parameters.AddWithValue("@time", DateTime.Now); + cmd.Parameters.AddWithValue("@strategy", strategy ?? "默认"); + cmd.ExecuteNonQuery(); + } + } + + /// + /// 获取路径的碰撞数量 + /// + public int GetCollisionCount(string routeId) + { + var sql = "SELECT COUNT(*) FROM CollisionResults WHERE RouteId=@id"; + + using (var cmd = new SQLiteCommand(sql, _connection)) + { + cmd.Parameters.AddWithValue("@id", routeId); + var result = cmd.ExecuteScalar(); + return Convert.ToInt32(result); + } + } + + /// + /// 获取路径的分析结果 + /// + public PathAnalysisResult GetAnalysisResult(string routeId) + { + var sql = "SELECT * FROM AnalysisResults WHERE RouteId=@id"; + + using (var cmd = new SQLiteCommand(sql, _connection)) + { + cmd.Parameters.AddWithValue("@id", routeId); + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + return new PathAnalysisResult + { + RouteId = reader["RouteId"].ToString(), + CollisionCount = Convert.ToInt32(reader["CollisionCount"]), + SafetyScore = Convert.ToDouble(reader["SafetyScore"]), + EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]), + OverallScore = Convert.ToDouble(reader["OverallScore"]), + AnalysisTime = Convert.ToDateTime(reader["AnalysisTime"]), + Strategy = reader["Strategy"].ToString() + }; + } + } + } + + return null; + } + + /// + /// 获取所有路径的分析结果 + /// + public List GetAllAnalysisResults() + { + var results = new List(); + var sql = "SELECT * FROM AnalysisResults ORDER BY OverallScore DESC"; + + using (var cmd = new SQLiteCommand(sql, _connection)) + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + results.Add(new PathAnalysisResult + { + RouteId = reader["RouteId"].ToString(), + CollisionCount = Convert.ToInt32(reader["CollisionCount"]), + SafetyScore = Convert.ToDouble(reader["SafetyScore"]), + EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]), + OverallScore = Convert.ToDouble(reader["OverallScore"]), + AnalysisTime = Convert.ToDateTime(reader["AnalysisTime"]), + Strategy = reader["Strategy"].ToString() + }); + } + } + + return results; + } + + /// + /// 删除路径及其相关数据 + /// + /// 路径ID + public void DeletePathRoute(string routeId) + { + try + { + // 由于设置了外键级联删除,删除路径时会自动删除相关的碰撞结果和分析结果 + var sql = "DELETE FROM PathRoutes WHERE Id = @id"; + using (var cmd = new SQLiteCommand(sql, _connection)) + { + cmd.Parameters.AddWithValue("@id", routeId); + int affected = cmd.ExecuteNonQuery(); + LogManager.Info($"删除路径记录: ID={routeId}, 影响行数={affected}"); + } + } + catch (Exception ex) + { + LogManager.Error($"删除路径失败: ID={routeId}, {ex.Message}", ex); + throw; + } + } + + /// + /// 获取所有路径基本信息 + /// + public List GetAllPathRoutes() + { + var routes = new List(); + var sql = @"SELECT pr.*, ar.CollisionCount, ar.SafetyScore, ar.EfficiencyScore, ar.OverallScore + FROM PathRoutes pr + LEFT JOIN AnalysisResults ar ON pr.Id = ar.RouteId + ORDER BY pr.LastModified DESC"; + + using (var cmd = new SQLiteCommand(sql, _connection)) + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var route = new PathRoute + { + Id = reader["Id"].ToString(), + Name = reader["Name"].ToString(), + TotalLength = Convert.ToDouble(reader["TotalLength"]), + EstimatedTime = Convert.ToDouble(reader["EstimatedTime"]), + CreatedTime = Convert.ToDateTime(reader["CreatedTime"]), + LastModified = Convert.ToDateTime(reader["LastModified"]) + }; + + // 添加分析结果(如果存在) + if (!reader.IsDBNull(reader.GetOrdinal("CollisionCount"))) + { + route.CollisionCount = Convert.ToInt32(reader["CollisionCount"]); + route.SafetyScore = Convert.ToDouble(reader["SafetyScore"]); + route.EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]); + route.OverallScore = Convert.ToDouble(reader["OverallScore"]); + } + + routes.Add(route); + } + } + + // 为每个路径加载路径点 + foreach (var route in routes) + { + LoadPathPoints(route); + } + + LogManager.Info($"从数据库加载了 {routes.Count} 条路径记录"); + return routes; + } + + /// + /// 加载指定路径的路径点 + /// + private void LoadPathPoints(PathRoute route) + { + var sql = @"SELECT * FROM PathPoints + WHERE RouteId=@routeId + ORDER BY SequenceNumber"; + + using (var cmd = new SQLiteCommand(sql, _connection)) + { + cmd.Parameters.AddWithValue("@routeId", route.Id); + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var point = new PathPoint + { + Id = reader["Id"].ToString(), + Name = reader["Name"].ToString(), + Position = new Autodesk.Navisworks.Api.Point3D( + Convert.ToDouble(reader["X"]), + Convert.ToDouble(reader["Y"]), + Convert.ToDouble(reader["Z"]) + ), + Type = (PathPointType)Convert.ToInt32(reader["Type"]) + }; + + route.Points.Add(point); + } + } + } + + LogManager.Info($"为路径 '{route.Name}' 加载了 {route.Points.Count} 个路径点"); + } + + /// + /// 执行非查询SQL语句 + /// + private void ExecuteNonQuery(string sql, object parameters = null) + { + using (var cmd = new SQLiteCommand(sql, _connection)) + { + if (parameters != null) + { + var props = parameters.GetType().GetProperties(); + foreach (var prop in props) + { + cmd.Parameters.AddWithValue($"@{prop.Name}", prop.GetValue(parameters)); + } + } + cmd.ExecuteNonQuery(); + } + } + + /// + /// 获取ModelItem的显示名称 + /// + private string GetItemDisplayName(ModelItem item) + { + if (item == null) + return "未知对象"; + + try + { + return item.DisplayName ?? item.ToString(); + } + catch + { + return "未知对象"; + } + } + + /// + /// 释放资源 + /// + public void Dispose() + { + _connection?.Close(); + _connection?.Dispose(); + } + } + + /// + /// 路径分析结果数据模型 + /// + public class PathAnalysisResult + { + public string RouteId { get; set; } + public int CollisionCount { get; set; } + public double SafetyScore { get; set; } + public double EfficiencyScore { get; set; } + public double OverallScore { get; set; } + public DateTime AnalysisTime { get; set; } + public string Strategy { get; set; } + } +} diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index cca9077..b90f418 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -25,6 +25,10 @@ namespace NavisworksTransport private CoordinateConverter _coordinateConverter; private PathPointRenderPlugin _renderPlugin; + + // 路径分析相关 + private PathDatabase _pathDatabase; + private PathAnalysisService _analysisService; private List _walkableAreas; private List _routes; private PathRoute _currentRoute; @@ -111,6 +115,40 @@ namespace NavisworksTransport _activePathManager = this; LogManager.WriteLog($"[路径管理] 设置_activePathManager静态引用, ManagerId: {_managerId}"); + // 尝试初始化路径分析数据库 + EnsureDatabaseInitialized(); + } + + /// + /// 确保路径分析数据库已初始化(支持延迟初始化) + /// + public void EnsureDatabaseInitialized() + { + if (_pathDatabase != null && _analysisService != null) + { + LogManager.Info("路径分析数据库已初始化,跳过"); + return; + } + + try + { + var documentPath = Autodesk.Navisworks.Api.Application.ActiveDocument?.FileName; + if (!string.IsNullOrEmpty(documentPath)) + { + _pathDatabase = new PathDatabase(documentPath); + _analysisService = new PathAnalysisService(_pathDatabase); + LogManager.Info($"路径分析数据库初始化成功,文档路径: {documentPath}"); + } + else + { + LogManager.Warning("文档路径为空,路径分析数据库初始化将延迟到文档加载后"); + } + } + catch (Exception ex) + { + LogManager.Error($"初始化路径分析数据库失败: {ex.Message}", ex); + } + // 获取已注册的圆形渲染插件实例 try { @@ -119,7 +157,7 @@ namespace NavisworksTransport { LogManager.WriteLog("[路径管理] ✅ PathPointRenderPlugin实例获取成功"); LogManager.WriteLog($"[路径管理] 渲染插件状态 - 启用: {_renderPlugin.IsEnabled}, 标记数量: {_renderPlugin.MarkerCount}"); - + // 推送默认的网格大小和车辆参数,确保渲染插件有合理的初始值 InitializeRenderPluginDefaults(); } @@ -142,6 +180,9 @@ namespace NavisworksTransport _coordinateConverter = null; // 将在需要时初始化 _pathPointMarkers = new List(); + // 从数据库加载历史路径到内存 + LoadHistoricalRoutesFromDatabase(); + LogManager.Info($"PathPlanningManager初始化完成,ManagerId: {_managerId}"); } @@ -163,14 +204,14 @@ namespace NavisworksTransport const double defaultVehicleWidth = 1.0; // 米 const double defaultVehicleHeight = 2.0; // 米 const double defaultSafetyMargin = 0.25; // 米 - + _renderPlugin.SetVehicleParameters( defaultVehicleLength, - defaultVehicleWidth, + defaultVehicleWidth, defaultVehicleHeight, defaultSafetyMargin ); - + LogManager.Info($"[渲染插件初始化] 已设置默认车辆参数: 长={defaultVehicleLength}m, 宽={defaultVehicleWidth}m, 高={defaultVehicleHeight}m, 安全间隙={defaultSafetyMargin}m"); } catch (Exception ex) @@ -679,10 +720,10 @@ namespace NavisworksTransport } /// - /// 从3D中移除路径点 + /// 删除路径点 /// - /// 要移除的路径点 - public void RemovePathPointFrom3D(PathPoint point) + /// 要删除的路径点 + public void RemovePathPoint(PathPoint point) { try { @@ -708,6 +749,12 @@ namespace NavisworksTransport // 触发路径点移除事件 RaisePathPointOperation(PathPointOperationType.Removed, point, CurrentRoute); + + // 保存到数据库 + if (CurrentRoute != null) + { + SavePathToDatabase(CurrentRoute); + } } } catch (Exception ex) @@ -991,6 +1038,8 @@ namespace NavisworksTransport if (!_routes.Contains(autoRoute)) { _routes.Add(autoRoute); + // 保存自动生成的路径到数据库 + SavePathToDatabase(autoRoute); } CurrentRoute = autoRoute; @@ -1187,6 +1236,9 @@ namespace NavisworksTransport _routes.Add(route); RaiseStatusChanged($"已添加路径: {route.Name}", PathPlanningStatusType.Success); + // 保存到数据库 + SavePathToDatabase(route); + // AddRoute只负责添加路径到数据集合,不自动设置当前路径 // 路径选择应该通过专门的选择逻辑处理,而不是添加操作的副作用 // if (_currentRoute == null || _currentRoute.Points.Count == 0) @@ -1222,6 +1274,9 @@ namespace NavisworksTransport _routes.Add(newRoute); CurrentRoute = newRoute; + // 保存到数据库 + SavePathToDatabase(newRoute); + RaiseStatusChanged($"已创建新路径: {routeName}", PathPlanningStatusType.Success); return newRoute; } @@ -1240,6 +1295,21 @@ namespace NavisworksTransport bool removed = _routes.Remove(route); if (removed) { + // 从数据库删除 + if (_pathDatabase != null) + { + try + { + _pathDatabase.DeletePathRoute(route.Id); + LogManager.Info($"已从数据库删除路径: {route.Name}"); + } + catch (Exception dbEx) + { + LogManager.Error($"从数据库删除路径失败: {dbEx.Message}", dbEx); + // 继续执行,不影响内存操作 + } + } + if (_currentRoute == route) { var newRoute = _routes.FirstOrDefault() ?? new PathRoute("默认路径"); @@ -1379,6 +1449,9 @@ namespace NavisworksTransport { _routes.Add(CurrentRoute); + // 保存到数据库 + SavePathToDatabase(CurrentRoute); + // 添加历史记录 var historyEntry = new PathHistoryEntry( CurrentRoute.Id, @@ -1392,6 +1465,9 @@ namespace NavisworksTransport // 如果是编辑模式,保存编辑历史 if (_pathEditState == PathEditState.Editing && EditingRoute != null) { + // 更新数据库 + SavePathToDatabase(EditingRoute); + var historyEntry = new PathHistoryEntry( EditingRoute.Id, PathHistoryOperationType.Edited, @@ -1498,10 +1574,10 @@ namespace NavisworksTransport } /// - /// 确认修改路径点 + /// 更新路径点位置(保存修改) /// - /// 是否成功确认修改 - public bool ConfirmEditPoint() + /// 是否成功更新 + public bool UpdatePointPosition() { try { @@ -1541,6 +1617,9 @@ namespace NavisworksTransport // 触发路径点列表更新事件 RaisePathPointsListUpdated(CurrentRoute, "路径点修改完成"); + // 保存到数据库 + SavePathToDatabase(CurrentRoute); + RaiseStatusChanged($"路径点 {pointToUpdate.Name} 修改完成", PathPlanningStatusType.Success); LogManager.Info($"路径点修改完成: {pointToUpdate.Name}"); @@ -1548,7 +1627,7 @@ namespace NavisworksTransport } catch (Exception ex) { - RaiseErrorOccurred($"确认修改路径点失败: {ex.Message}", ex); + RaiseErrorOccurred($"确认修改路径点失败: {ex.Message}"); return false; } } @@ -1852,10 +1931,10 @@ namespace NavisworksTransport } /// - /// 确认预览点,将其添加到当前路径中 + /// 将预览点添加为正式路径点 /// /// 添加的路径点,如果失败返回null - public PathPoint ConfirmPreviewPoint() + public PathPoint ConvertPreviewToPathPoint() { if (!_isPreviewMode || _previewPoint == null) { @@ -1913,11 +1992,14 @@ namespace NavisworksTransport // 触发路径列表更新事件 RaisePathPointsListUpdated(CurrentRoute, "确认添加预览点"); + // 保存到数据库 + SavePathToDatabase(CurrentRoute); + return confirmPoint; } catch (Exception ex) { - RaiseErrorOccurred($"确认预览点失败: {ex.Message}", ex); + RaiseErrorOccurred($"确认预览点失败: {ex.Message}"); return null; } } @@ -3482,6 +3564,166 @@ namespace NavisworksTransport #endregion + #region 路径分析方法 + + /// + /// 获取所有路径(供分析使用) + /// + public List GetAllRoutes() + { + try + { + // 直接返回内存中的路径列表 + // 因为所有路径操作都已同步到数据库,内存列表就是当前的完整列表 + return _routes ?? new List(); + } + catch (Exception ex) + { + LogManager.Error($"获取所有路径失败: {ex.Message}", ex); + return new List(); + } + } + + /// + /// 分析多条路径并进行对比 + /// + /// 要分析的路径ID列表 + /// 分析策略 + /// 对比结果 + public PathComparisonResult AnalyzePathsAndCompare(List routeIds, string strategy) + { + try + { + if (_analysisService == null) + { + LogManager.Error("路径分析服务未初始化"); + return null; + } + + // 获取要分析的路径 + var routesToAnalyze = _routes?.Where(r => routeIds.Contains(r.Id)).ToList(); + if (routesToAnalyze == null || routesToAnalyze.Count == 0) + { + LogManager.Warning("未找到要分析的路径"); + return null; + } + + // 执行分析和对比 + var result = _analysisService.CompareRoutes(routesToAnalyze, strategy); + + return result; + } + catch (Exception ex) + { + LogManager.Error($"路径分析失败: {ex.Message}", ex); + return null; + } + } + + /// + /// 从数据库加载历史路径到内存 + /// + private void LoadHistoricalRoutesFromDatabase() + { + try + { + if (_pathDatabase == null) + { + LogManager.Info("数据库未初始化,跳过加载历史路径"); + return; + } + + var historicalRoutes = _pathDatabase.GetAllPathRoutes(); + if (historicalRoutes != null && historicalRoutes.Count > 0) + { + // 使用HashSet记录已存在的路径ID,避免重复 + var existingIds = new HashSet(_routes.Select(r => r.Id)); + int loadedCount = 0; + + // 加载历史路径(跳过已存在的) + foreach (var route in historicalRoutes) + { + if (!existingIds.Contains(route.Id)) + { + _routes.Add(route); + loadedCount++; + LogManager.Info($"加载历史路径: {route.Name}, ID={route.Id}, 长度={route.TotalLength:F2}米"); + } + } + + if (loadedCount > 0) + { + LogManager.Info($"成功从数据库加载 {loadedCount} 条历史路径(总计 {historicalRoutes.Count} 条)"); + } + else + { + LogManager.Info($"数据库中的 {historicalRoutes.Count} 条路径已全部在内存中"); + } + + // 如果当前没有选中的路径,且路径列表不为空,选择第一条 + if ((_currentRoute == null || string.IsNullOrEmpty(_currentRoute.Name) || _currentRoute.Name == "默认路径") + && _routes.Count > 0) + { + _currentRoute = _routes[0]; + LogManager.Info($"设置当前路径为: {_currentRoute.Name}"); + } + + // 历史路径加载完成后,触发RouteGenerated事件通知UI刷新 + // 使用特殊的生成方法标记这是数据库加载 + foreach (var route in _routes) + { + RaiseRouteGenerated(route, RouteGenerationMethod.DatabaseLoad); + } + LogManager.Info($"已触发{_routes.Count}个RouteGenerated事件,通知UI刷新历史路径"); + } + else + { + LogManager.Info("数据库中没有历史路径记录"); + } + } + catch (Exception ex) + { + LogManager.Error($"加载历史路径失败: {ex.Message}", ex); + } + } + + /// + /// 保存路径时同时保存到数据库 + /// + private void SavePathToDatabase(PathRoute route) + { + try + { + if (_pathDatabase != null && route != null) + { + _pathDatabase.SavePathRoute(route); + LogManager.Info($"路径已保存到数据库: {route.Name}"); + } + } + catch (Exception ex) + { + LogManager.Error($"保存路径到数据库失败: {ex.Message}", ex); + } + } + + /// + /// 获取Instance单例(为兼容现有代码) + /// + public static PathPlanningManager Instance + { + get + { + if (_activePathManager == null) + { + LogManager.Warning("PathPlanningManager未初始化,创建新实例"); + _activePathManager = new PathPlanningManager(); + } + return _activePathManager; + } + } + + #endregion + #region 静态方法 /// diff --git a/src/Core/PathPlanningManagerEventArgs.cs b/src/Core/PathPlanningManagerEventArgs.cs index 373eb0b..8907fed 100644 --- a/src/Core/PathPlanningManagerEventArgs.cs +++ b/src/Core/PathPlanningManagerEventArgs.cs @@ -346,7 +346,12 @@ namespace NavisworksTransport.Core /// /// 优化 /// - Optimization + Optimization, + + /// + /// 从数据库加载 + /// + DatabaseLoad } #endregion diff --git a/src/Core/PathPlanningModels.cs b/src/Core/PathPlanningModels.cs index 7afe631..0c2ca47 100644 --- a/src/Core/PathPlanningModels.cs +++ b/src/Core/PathPlanningModels.cs @@ -408,6 +408,27 @@ namespace NavisworksTransport /// public double VehicleHeight { get; set; } = 2.0; + // 数据库分析相关属性 + /// + /// 碰撞数量(从数据库加载) + /// + public int? CollisionCount { get; set; } + + /// + /// 安全评分(从数据库加载) + /// + public double? SafetyScore { get; set; } + + /// + /// 效率评分(从数据库加载) + /// + public double? EfficiencyScore { get; set; } + + /// + /// 综合评分(从数据库加载) + /// + public double? OverallScore { get; set; } + /// /// 构造函数 /// diff --git a/src/UI/WPF/ViewModels/PathAnalysisViewModel.cs b/src/UI/WPF/ViewModels/PathAnalysisViewModel.cs index d4e69a2..2a01fe0 100644 --- a/src/UI/WPF/ViewModels/PathAnalysisViewModel.cs +++ b/src/UI/WPF/ViewModels/PathAnalysisViewModel.cs @@ -4,7 +4,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; -using NavisworksTransport.Utils; +using System.Windows.Input; namespace NavisworksTransport.UI.WPF.ViewModels { @@ -31,7 +31,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels public ObservableCollection AvailablePaths { get; set; } /// - /// 选中的路径列表(用于对比分析) + /// 选中的路径列表(用于对比分析和结果显示) /// public ObservableCollection SelectedPaths { get; set; } @@ -99,6 +99,26 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// public ObservableCollection BottleneckLocations { get; set; } + /// + /// 开始分析命令 + /// + public ICommand StartAnalysisCommand { get; private set; } + + /// + /// 全选命令 + /// + public ICommand SelectAllCommand { get; private set; } + + /// + /// 清空选择命令 + /// + public ICommand ClearSelectionCommand { get; private set; } + + /// + /// 导出报告命令 + /// + public ICommand ExportReportCommand { get; private set; } + #endregion #region 构造函数 @@ -111,7 +131,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels try { InitializeCollections(); - LoadDemoData(); + InitializeCommands(); + + // 确保PathPlanningManager的数据库已初始化 + EnsurePathDatabaseInitialized(); + + LoadPathData(); LogManager.Info("PathAnalysisViewModel初始化完成"); } catch (Exception ex) @@ -120,6 +145,43 @@ namespace NavisworksTransport.UI.WPF.ViewModels } } + /// + /// 初始化命令 + /// + private void InitializeCommands() + { + StartAnalysisCommand = new RelayCommand(ExecuteStartAnalysis, CanExecuteStartAnalysis); + SelectAllCommand = new RelayCommand(SelectAll); + ClearSelectionCommand = new RelayCommand(ClearSelection); + ExportReportCommand = new RelayCommand(ExportReport, CanExportReport); + } + + private bool CanExecuteStartAnalysis() + { + return !IsAnalyzing && AvailablePaths.Any(p => p.IsSelected); + } + + private void ExecuteStartAnalysis() + { + PerformAnalysis(); + } + + /// + /// 确保PathPlanningManager的路径数据库已初始化 + /// + private void EnsurePathDatabaseInitialized() + { + try + { + var manager = PathPlanningManager.Instance; + manager.EnsureDatabaseInitialized(); + } + catch (Exception ex) + { + LogManager.Error($"确保数据库初始化失败: {ex.Message}", ex); + } + } + #endregion #region 初始化方法 @@ -136,163 +198,109 @@ namespace NavisworksTransport.UI.WPF.ViewModels } /// - /// 加载演示数据 + /// 加载真实路径数据 /// - private void LoadDemoData() + private void LoadPathData() { try { - // 加载演示路径数据 - var demoPath1 = new PathAnalysisItem + // 从PathPlanningManager获取所有路径(包括数据库中的历史路径) + var allRoutes = PathPlanningManager.Instance.GetAllRoutes(); + + if (allRoutes == null || allRoutes.Count == 0) { - Name = "自动路径_20250103_141523", - Type = "自动生成", - Length = 98.5, - EstimatedTime = 147, - CollisionCount = 2, - AffectedObjects = 5, - AverageSpeed = 0.67, - Score = 82, - IsSelected = true - }; + LogManager.Info("当前没有可用的路径数据"); + AnalysisStatus = "暂无可用路径数据,请先规划路径"; + return; + } - var demoPath2 = new PathAnalysisItem + // 清空现有数据 + AvailablePaths.Clear(); + + // 将路径数据转换为UI显示项 + foreach (var route in allRoutes) { - Name = "手动路径_20250103_102045", - Type = "手动规划", - Length = 125.3, - EstimatedTime = 188, - CollisionCount = 0, - AffectedObjects = 3, - AverageSpeed = 0.67, - Score = 95, - IsSelected = true - }; + var item = new PathAnalysisItem + { + RouteId = route.Id, + Name = route.Name, + Type = route.CreatedTime != default(DateTime) ? "历史路径" : "当前会话", + Length = Math.Round(route.TotalLength, 2), + EstimatedTime = (int)route.EstimatedTime, + IsSelected = false + }; - var demoPath3 = new PathAnalysisItem - { - Name = "自动路径_20250103_093012", - Type = "自动生成", - Length = 105.8, - EstimatedTime = 159, - CollisionCount = 4, - AffectedObjects = 8, - AverageSpeed = 0.67, - Score = 68, - IsSelected = false - }; + // 如果数据库中有分析结果,加载相关数据 + if (route.CollisionCount.HasValue) + { + item.CollisionCount = route.CollisionCount.Value; + item.Score = (int)route.OverallScore.GetValueOrDefault(0); + item.AffectedObjects = route.CollisionCount.Value; // 简化:使用碰撞数作为受影响对象数 + } + else + { + item.CollisionCount = 0; + item.Score = 0; + item.AffectedObjects = 0; + } - var demoPath4 = new PathAnalysisItem - { - Name = "手动路径_20250102_160230", - Type = "手动规划", - Length = 132.6, - EstimatedTime = 199, - CollisionCount = 1, - AffectedObjects = 4, - AverageSpeed = 0.67, - Score = 78, - IsSelected = false - }; + // 计算平均速度 + if (route.EstimatedTime > 0) + { + item.AverageSpeed = Math.Round(route.TotalLength / route.EstimatedTime, 2); + } - AvailablePaths.Add(demoPath1); - AvailablePaths.Add(demoPath2); - AvailablePaths.Add(demoPath3); - AvailablePaths.Add(demoPath4); + // 监听选择状态变化,以刷新命令状态 + item.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(PathAnalysisItem.IsSelected)) + { + CommandManager.InvalidateRequerySuggested(); + } + }; - // 添加选中的路径到对比列表 - SelectedPaths.Add(demoPath1); - SelectedPaths.Add(demoPath2); + AvailablePaths.Add(item); + } - // 加载优化建议 - LoadOptimizationSuggestions(); - - // 加载瓶颈位置 - LoadBottleneckLocations(); - - // 计算推荐路径 - UpdateRecommendedPath(); - - LogManager.Info("路径分析演示数据加载完成"); + LogManager.Info($"加载路径数据完成:共{AvailablePaths.Count}条路径(包括历史路径)"); + AnalysisStatus = $"已加载 {AvailablePaths.Count} 条路径,请选择要分析的路径"; } catch (Exception ex) { - LogManager.Error($"加载路径分析演示数据失败: {ex.Message}", ex); + LogManager.Error($"加载真实路径数据失败: {ex.Message}", ex); + AnalysisStatus = "加载路径数据失败"; } } - /// - /// 加载优化建议 - /// - private void LoadOptimizationSuggestions() - { - OptimizationSuggestions.Add(new OptimizationSuggestion - { - Title = "在电梯口设置等待区", - Description = "避免多路径在电梯处产生拥堵,提高通行效率", - Priority = "高", - Status = "建议采纳" - }); - - OptimizationSuggestions.Add(new OptimizationSuggestion - { - Title = "优化转弯半径至2.0米", - Description = "当前1.5米转弯半径过小,增大至2.0米可提高安全性", - Priority = "高", - Status = "建议采纳" - }); - - OptimizationSuggestions.Add(new OptimizationSuggestion - { - Title = "调整作业时间窗口", - Description = "建议在09:00-10:00执行,避开人员高峰期", - Priority = "中", - Status = "可选" - }); - - OptimizationSuggestions.Add(new OptimizationSuggestion - { - Title = "考虑分批运输", - Description = "单次运输量过大时,可分2批执行降低碰撞风险", - Priority = "中", - Status = "可选" - }); - } - - /// - /// 加载瓶颈位置 - /// - private void LoadBottleneckLocations() - { - BottleneckLocations.Add(new BottleneckLocation - { - Name = "电梯口", - AffectedPaths = 2, - Severity = "高风险", - Suggestion = "设置等待区" - }); - - BottleneckLocations.Add(new BottleneckLocation - { - Name = "通道转角", - AffectedPaths = 2, - Severity = "中等风险", - Suggestion = "优化转弯半径" - }); - - BottleneckLocations.Add(new BottleneckLocation - { - Name = "装卸区入口", - AffectedPaths = 1, - Severity = "低风险", - Suggestion = "无需调整" - }); - } #endregion #region 业务逻辑方法 + /// + /// 全选所有路径 + /// + private void SelectAll() + { + foreach (var path in AvailablePaths) + { + path.IsSelected = true; + } + CommandManager.InvalidateRequerySuggested(); + } + + /// + /// 清空所有选择 + /// + private void ClearSelection() + { + foreach (var path in AvailablePaths) + { + path.IsSelected = false; + } + CommandManager.InvalidateRequerySuggested(); + } + /// /// 更新推荐路径 /// @@ -348,15 +356,41 @@ namespace NavisworksTransport.UI.WPF.ViewModels IsAnalyzing = true; AnalysisStatus = "正在分析..."; - // 模拟分析过程 - System.Threading.Tasks.Task.Delay(2000).ContinueWith(t => + // 获取选中的路径 + var selectedRoutes = AvailablePaths.Where(p => p.IsSelected).ToList(); + if (selectedRoutes.Count == 0) { + AnalysisStatus = "请选择要分析的路径"; IsAnalyzing = false; - UpdateRecommendedPath(); - AnalysisStatus = "分析完成"; - }); + return; + } - LogManager.Info("开始执行路径分析"); + // 更新选中路径列表用于UI显示 + SelectedPaths.Clear(); + foreach (var route in selectedRoutes) + { + SelectedPaths.Add(route); + } + + // 执行真实的路径分析 + var result = PathPlanningManager.Instance.AnalyzePathsAndCompare( + selectedRoutes.Select(p => p.RouteId).ToList(), + AnalysisStrategy + ); + + if (result != null) + { + // 更新UI数据 + UpdateAnalysisResults(result); + AnalysisStatus = "分析完成"; + LogManager.Info($"路径分析完成,最佳路径: {result.BestRouteName}"); + } + else + { + AnalysisStatus = "分析失败"; + } + + IsAnalyzing = false; } catch (Exception ex) { @@ -366,6 +400,143 @@ namespace NavisworksTransport.UI.WPF.ViewModels } } + /// + /// 导出报告 + /// + private void ExportReport() + { + try + { + if (RecommendedPath == null || SelectedPaths.Count == 0) + { + LogManager.Warning("没有分析结果可以导出"); + return; + } + + // 生成报告文件名 + var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); + var fileName = $"路径分析报告_{timestamp}.txt"; + var filePath = System.IO.Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Desktop), + fileName + ); + + // 生成报告内容 + var report = new System.Text.StringBuilder(); + report.AppendLine("===================================="); + report.AppendLine(" 物流路径分析报告"); + report.AppendLine("===================================="); + report.AppendLine(); + report.AppendLine($"生成时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); + report.AppendLine($"分析策略:{AnalysisStrategy}"); + report.AppendLine(); + + report.AppendLine("【分析路径列表】"); + report.AppendLine("----------------------------------------"); + foreach (var path in SelectedPaths) + { + report.AppendLine($"路径名称:{path.Name}"); + report.AppendLine($" - 长度:{path.Length:F2} 米"); + report.AppendLine($" - 预估时间:{path.EstimatedTime} 秒"); + report.AppendLine($" - 碰撞数量:{path.CollisionCount}"); + report.AppendLine($" - 综合评分:{path.Score}"); + report.AppendLine(); + } + + report.AppendLine("【推荐路径】"); + report.AppendLine("----------------------------------------"); + if (RecommendedPath != null) + { + report.AppendLine($"推荐路径:{RecommendedPath.Name}"); + report.AppendLine($"推荐理由:基于{AnalysisStrategy}策略,该路径具有最优综合评分"); + report.AppendLine(); + } + + report.AppendLine("【优化建议】"); + report.AppendLine("----------------------------------------"); + if (OptimizationSuggestions.Count > 0) + { + int index = 1; + foreach (var suggestion in OptimizationSuggestions) + { + report.AppendLine($"{index}. {suggestion.Title}"); + index++; + } + } + else + { + report.AppendLine("暂无优化建议"); + } + + // 写入文件 + System.IO.File.WriteAllText(filePath, report.ToString(), System.Text.Encoding.UTF8); + + AnalysisStatus = $"报告已导出到:{filePath}"; + LogManager.Info($"分析报告已导出:{filePath}"); + + // 打开文件所在目录并选中文件 + System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{filePath}\""); + } + catch (Exception ex) + { + LogManager.Error($"导出报告失败:{ex.Message}", ex); + AnalysisStatus = "导出报告失败"; + } + } + + /// + /// 判断是否可以导出报告 + /// + private bool CanExportReport() + { + return SelectedPaths != null && SelectedPaths.Count > 0 && RecommendedPath != null; + } + + /// + /// 更新分析结果到UI + /// + private void UpdateAnalysisResults(PathComparisonResult result) + { + // 更新路径的碰撞和评分数据 + foreach (var analysis in result.Analyses) + { + // 更新AvailablePaths中的数据 + var pathItem = AvailablePaths.FirstOrDefault(p => p.RouteId == analysis.RouteId); + if (pathItem != null) + { + pathItem.CollisionCount = analysis.CollisionCount; + pathItem.Score = (int)analysis.OverallScore; + pathItem.AverageSpeed = pathItem.Length / pathItem.EstimatedTime; + pathItem.AffectedObjects = analysis.CollisionCount; // 简化:使用碰撞数作为受影响对象数 + } + + // 同时更新SelectedPaths中的数据 + var selectedItem = SelectedPaths.FirstOrDefault(p => p.RouteId == analysis.RouteId); + if (selectedItem != null) + { + selectedItem.CollisionCount = analysis.CollisionCount; + selectedItem.Score = (int)analysis.OverallScore; + selectedItem.AverageSpeed = selectedItem.Length / selectedItem.EstimatedTime; + selectedItem.AffectedObjects = analysis.CollisionCount; + } + } + + // 设置推荐路径 + RecommendedPath = SelectedPaths.FirstOrDefault(p => p.RouteId == result.BestRouteId); + + // 更新优化建议 + OptimizationSuggestions.Clear(); + foreach (var suggestion in result.Suggestions) + { + OptimizationSuggestions.Add(new OptimizationSuggestion + { + Title = suggestion, + Priority = "中", + Status = "待处理" + }); + } + } + #endregion #region INotifyPropertyChanged实现 @@ -385,8 +556,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// /// 路径分析项目 /// - public class PathAnalysisItem + public class PathAnalysisItem : INotifyPropertyChanged { + private bool _isSelected; + private int _collisionCount; + private int _affectedObjects; + private double _averageSpeed; + private int _score; + + /// 路径ID + public string RouteId { get; set; } + /// 路径名称 public string Name { get; set; } @@ -400,19 +580,66 @@ namespace NavisworksTransport.UI.WPF.ViewModels public int EstimatedTime { get; set; } /// 碰撞次数 - public int CollisionCount { get; set; } + public int CollisionCount + { + get => _collisionCount; + set + { + _collisionCount = value; + OnPropertyChanged(); + } + } /// 受影响对象数 - public int AffectedObjects { get; set; } + public int AffectedObjects + { + get => _affectedObjects; + set + { + _affectedObjects = value; + OnPropertyChanged(); + } + } /// 平均速度(m/s) - public double AverageSpeed { get; set; } + public double AverageSpeed + { + get => _averageSpeed; + set + { + _averageSpeed = value; + OnPropertyChanged(); + } + } /// 综合评分 - public int Score { get; set; } + public int Score + { + get => _score; + set + { + _score = value; + OnPropertyChanged(); + } + } /// 是否被选中用于分析 - public bool IsSelected { get; set; } + public bool IsSelected + { + get => _isSelected; + set + { + _isSelected = value; + OnPropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } /// diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index b6955a8..60a2aff 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -1174,7 +1174,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (isEditingPoint) { // 修改路径点模式 - 尝试确认修改 - success = _pathPlanningManager.ConfirmEditPoint(); + success = _pathPlanningManager.UpdatePointPosition(); if (success) { operationMessage = "路径点修改已确认"; @@ -1195,7 +1195,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (wasInPreviewMode) { // 预览模式 - 先确认预览点,然后立即完成编辑 - var confirmedPoint = _pathPlanningManager.ConfirmPreviewPoint(); + var confirmedPoint = _pathPlanningManager.ConvertPreviewToPathPoint(); if (confirmedPoint != null) { LogManager.Info($"[UI-预览模式] 预览点已确认: {confirmedPoint.Name},现在完成编辑"); @@ -1344,7 +1344,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels LogManager.Info($"已从Core数据删除路径点: {corePoint.Name}"); // 调用PathPlanningManager的3D删除方法 - _pathPlanningManager.RemovePathPointFrom3D(corePoint); + _pathPlanningManager.RemovePathPoint(corePoint); // 触发UI同步更新事件 _pathPlanningManager.RaisePathPointsListUpdated(coreRoute, "删除路径点并重新分配类型"); @@ -2209,7 +2209,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels _pathPlanningManager.ErrorOccurred += OnPathPlanningError; LogManager.Info("*** PathEditingViewModel已成功订阅PathPlanningManager事件,包括RouteGenerated和ErrorOccurred ***"); - + // 验证事件订阅是否成功 var routeGeneratedField = _pathPlanningManager.GetType().GetEvent("RouteGenerated"); if (routeGeneratedField != null) @@ -2220,6 +2220,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels { LogManager.Error("*** RouteGenerated事件不存在!!! ***"); } + + // 初始化加载现有路径(包括从数据库加载的历史路径) + RefreshPathRoutes(); + LogManager.Info("*** 已从PathPlanningManager加载现有路径到UI ***"); } catch (Exception ex) { @@ -2604,6 +2608,49 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 手工路径生成(如果需要特殊处理) LogManager.Info($"手工路径生成完成: {e.Route.Name}"); } + else if (e.GenerationMethod == RouteGenerationMethod.DatabaseLoad) + { + // 从数据库加载的历史路径 + LogManager.Info($"*** 开始处理数据库加载路径: {e.Route.Name} ***"); + + // 创建对应的 WPF ViewModel + var dbPathViewModel = new PathRouteViewModel + { + Name = e.Route.Name, + Description = e.Route.Description, + IsActive = false // 历史路径默认不激活 + }; + + // 转换路径点 + foreach (var corePoint in e.Route.Points) + { + var wpfPoint = new PathPointViewModel + { + Id = corePoint.Id, + Name = corePoint.Name, + X = corePoint.X, + Y = corePoint.Y, + Z = corePoint.Z, + Type = corePoint.Type + }; + dbPathViewModel.Points.Add(wpfPoint); + } + + LogManager.Info($"*** 已创建PathRouteViewModel(数据库路径),包含 {dbPathViewModel.Points.Count} 个点 ***"); + + // 检查是否已存在同名路径,避免重复添加 + var existingPath = PathRoutes.FirstOrDefault(p => p.Name == e.Route.Name); + if (existingPath == null) + { + // 添加到 UI 列表 + PathRoutes.Add(dbPathViewModel); + LogManager.Info($"*** 数据库路径已添加到UI列表: {e.Route.Name},当前PathRoutes.Count = {PathRoutes.Count} ***"); + } + else + { + LogManager.Info($"*** 数据库路径已存在,跳过: {e.Route.Name} ***"); + } + } }, "处理路径生成事件"); } catch (Exception ex) diff --git a/src/UI/WPF/ViewModels/TimeTagViewModel.cs b/src/UI/WPF/ViewModels/TimeTagViewModel.cs index 4b295e6..dd85090 100644 --- a/src/UI/WPF/ViewModels/TimeTagViewModel.cs +++ b/src/UI/WPF/ViewModels/TimeTagViewModel.cs @@ -161,7 +161,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (route == null) { LogManager.Info("路径为空,加载演示数据"); - LoadDemoData(); return; } @@ -173,7 +172,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (!result.IsSuccessful) { LogManager.Warning($"时标计算失败: {result.ErrorMessage},回退到演示数据"); - LoadDemoData(); return; } @@ -211,8 +209,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels catch (Exception ex) { LogManager.Error($"加载路径时间数据失败: {ex.Message}", ex); - // 加载失败时回退到演示数据 - LoadDemoData(); } } @@ -248,48 +244,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels } } - /// - /// 加载演示数据 - /// - private void LoadDemoData() - { - try - { - // 加载时间分段演示数据 - TimeSegments.Add(new TimeSegmentItem - { - Index = 1, - Length = 9.70, - Speed = 0.80, - Duration = 12.1, - CumulativeTime = 12.1, - Remarks = "通道限速" - }); - - TimeSegments.Add(new TimeSegmentItem - { - Index = 2, - Length = 6.40, - Speed = 0.70, - Duration = 10.7, - CumulativeTime = 22.8, - Remarks = "拐角减速" - }); - - // 移除硬编码的等待时间,使用真实的路径数据 - - // 更新总时长并生成时间轴预览数据 - TotalDuration = 22.8; // 只计算实际路径时间 - GenerateTimelineItems(); - - LogManager.Info("时间标签演示数据加载完成"); - } - catch (Exception ex) - { - LogManager.Error($"加载时间标签演示数据失败: {ex.Message}", ex); - } - } - #endregion #region 业务逻辑方法 diff --git a/src/UI/WPF/Views/PathAnalysisDialog.xaml b/src/UI/WPF/Views/PathAnalysisDialog.xaml index 962cc8e..796e00a 100644 --- a/src/UI/WPF/Views/PathAnalysisDialog.xaml +++ b/src/UI/WPF/Views/PathAnalysisDialog.xaml @@ -118,82 +118,59 @@ NavisworksTransport 路径规划分析对话框 - 采用与主界面一致的Nav - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + -