# 批处理碰撞检测功能实施方案 **文档创建日期**: 2026年1月24日 **文档版本**: 1.0 **作者**: iFlow CLI --- ## 一、需求概述 基于用户需求,批处理碰撞检测功能需要实现以下核心能力: 1. **任务预设**:创建批处理任务,包含多条路径和各自的碰撞检测配置 2. **参数配置**: - 使用系统参数作为默认值(帧率、时长、检测间隙等) - 支持对每条路径进行单独配置(覆盖系统参数) - 支持指定运动物体/虚拟物体 - 支持指定被检测项(可选) 3. **队列执行**:简单队列,按顺序执行任务 4. **结果查看**: - 汇总报告(所有路径的统计和对比) - 单独报告(每条路径的详细报告) - 历史记录(数据库持久化) --- ## 二、核心设计原则 ### 2.1 复用现有系统 - ✅ **碰撞检测结果**:复用 `ClashDetectiveResultRecord` 和 `CollisionResult` - ✅ **数据库结构**:复用现有的 `ClashDetectiveResults` 和 `ClashDetectiveCollisionObjects` 表 - ✅ **单独报告**:复用 `GenerateCollisionReportCommand` 和 `CollisionReportDialog` - ✅ **汇总报告**:新开发(汇总多条路径的统计和对比) ### 2.2 解耦UI绑定 - 提取纯计算接口 `IBatchCollisionProcessor` - 批处理不涉及UI操作(鼠标光标、视图重绘、DispatcherTimer) - 所有Navisworks COM API调用仍在主线程执行 --- ## 三、数据库设计 ### 3.1 新增表结构 #### BatchTasks 表(批处理任务主表) ```sql CREATE TABLE BatchTasks ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, -- 任务名称 Description TEXT, -- 任务描述 CreatedTime TEXT NOT NULL, -- 创建时间(ISO 8601) Status TEXT NOT NULL, -- 任务状态:Pending, Running, Completed, Failed, Cancelled StartTime TEXT, -- 开始时间 EndTime TEXT, -- 结束时间 TotalItems INTEGER NOT NULL DEFAULT 0, -- 总任务项数 CompletedItems INTEGER NOT NULL DEFAULT 0, -- 已完成任务项数 FailedItems INTEGER NOT NULL DEFAULT 0, -- 失败任务项数 LastModified TEXT NOT NULL -- 最后修改时间 ); CREATE INDEX idx_batch_tasks_status ON BatchTasks(Status); CREATE INDEX idx_batch_tasks_created ON BatchTasks(CreatedTime); ``` #### BatchTaskItems 表(批处理任务项) ```sql CREATE TABLE BatchTaskItems ( Id INTEGER PRIMARY KEY AUTOINCREMENT, BatchTaskId INTEGER NOT NULL, -- 关联的批处理任务ID RouteId TEXT NOT NULL, -- 关联的路径ID(使用TEXT,与PathRoutes.Id一致) PathRouteName TEXT NOT NULL, -- 路径名称(冗余,方便查询) SequenceNumber INTEGER NOT NULL, -- 执行顺序 Status TEXT NOT NULL, -- 任务项状态:Pending, Running, Completed, Failed, Skipped StartTime TEXT, -- 开始时间 EndTime TEXT, -- 结束时间 ErrorMessage TEXT, -- 错误信息 ConfigOverride TEXT, -- 配置覆盖(JSON格式) ObjectObjectId TEXT, -- 运动物体ID(虚拟物体或真实物体) ObjectObjectName TEXT, -- 运动物体名称 DetectionItems TEXT, -- 被检测项列表(JSON格式,可选) ClashDetectiveTestName TEXT, -- 关联的ClashDetective测试名称(复用现有表) FOREIGN KEY (BatchTaskId) REFERENCES BatchTasks(Id) ON DELETE CASCADE ); CREATE INDEX idx_batch_task_items_batch ON BatchTaskItems(BatchTaskId); CREATE INDEX idx_batch_task_items_path ON BatchTaskItems(RouteId); CREATE INDEX idx_batch_task_items_status ON BatchTaskItems(Status); ``` ### 3.2 配置数据结构(JSON格式) ```json { "detectionGapMeters": 0.05, "frameRate": 30, "durationSeconds": 10.0, "collisionDetectionEnabled": true, "reportGenerationEnabled": true } ``` --- ## 四、核心类设计 ### 4.1 数据模型类 #### BatchCollisionTask.cs ```csharp namespace NavisworksTransport.Core.Models { public class BatchCollisionTask { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public DateTime CreatedTime { get; set; } public BatchTaskStatus Status { get; set; } public DateTime? StartTime { get; set; } public DateTime? EndTime { get; set; } public int TotalItems { get; set; } public int CompletedItems { get; set; } public int FailedItems { get; set; } public DateTime LastModified { get; set; } public List Items { get; set; } = new List(); } public enum BatchTaskStatus { Pending, Running, Completed, Failed, Cancelled } } ``` #### BatchTaskItem.cs ```csharp public class BatchTaskItem { public int Id { get; set; } public int BatchTaskId { get; set; } public string RouteId { get; set; } // 使用TEXT,与PathRoutes.Id一致 public string PathRouteName { get; set; } public int SequenceNumber { get; set; } public BatchTaskItemStatus Status { get; set; } public DateTime? StartTime { get; set; } public DateTime? EndTime { get; set; } public string ErrorMessage { get; set; } public CollisionDetectionConfig ConfigOverride { get; set; } public string ObjectObjectId { get; set; } public string ObjectObjectName { get; set; } public List DetectionItems { get; set; } = new List(); public string ClashDetectiveTestName { get; set; } // 关联到ClashDetectiveResults.TestName } public enum BatchTaskItemStatus { Pending, Running, Completed, Failed, Skipped } ``` #### CollisionDetectionConfig.cs ```csharp public class CollisionDetectionConfig { public double DetectionGapMeters { get; set; } = 0.05; public int FrameRate { get; set; } = 30; public double DurationSeconds { get; set; } = 10.0; public bool CollisionDetectionEnabled { get; set; } = true; public bool ReportGenerationEnabled { get; set; } = true; /// /// 合并配置(覆盖配置优先) /// public CollisionDetectionConfig MergeWith(CollisionDetectionConfig overrideConfig) { if (overrideConfig == null) return this; return new CollisionDetectionConfig { DetectionGapMeters = overrideConfig.DetectionGapMeters > 0 ? overrideConfig.DetectionGapMeters : DetectionGapMeters, FrameRate = overrideConfig.FrameRate > 0 ? overrideConfig.FrameRate : FrameRate, DurationSeconds = overrideConfig.DurationSeconds > 0 ? overrideConfig.DurationSeconds : DurationSeconds, CollisionDetectionEnabled = overrideConfig.CollisionDetectionEnabled, ReportGenerationEnabled = overrideConfig.ReportGenerationEnabled }; } } ``` ### 4.2 纯计算接口(解耦UI绑定) #### IBatchCollisionProcessor.cs ```csharp namespace NavisworksTransport.Core.Collision { /// /// 批处理碰撞检测纯计算接口 /// 关键特性:完全不涉及UI操作(鼠标光标、视图重绘、DispatcherTimer) /// public interface IBatchCollisionProcessor { /// /// 预计算动画帧和碰撞(纯计算,无UI操作) /// List PrecomputeFrames( PathRoute route, ModelItem animatedObject, bool isVirtualObject, double virtualObjectLength, double virtualObjectWidth, double virtualObjectHeight, int frameRate, double duration, double detectionGap, List manualDetectionTargets = null); /// /// 创建并运行ClashDetective测试(纯计算,无UI操作) /// 返回测试名称,用于关联到ClashDetectiveResults表 /// string CreateAndRunClashDetectiveTest( List precomputedCollisions, double detectionGap, string pathName, string routeId, ModelItem animatedObject, bool isVirtualObject, int frameRate, double duration, double virtualObjectLength, double virtualObjectWidth, double virtualObjectHeight); } } ``` ### 4.3 执行器类 #### BatchCollisionExecutor.cs ```csharp namespace NavisworksTransport.Core.Collision { public class BatchCollisionExecutor { private readonly PathDatabase _database; private readonly IBatchCollisionProcessor _processor; private readonly UIStateManager _uiStateManager; private CancellationTokenSource _cancellationTokenSource; public event EventHandler TaskProgress; public event EventHandler ItemCompleted; public BatchCollisionExecutor( PathDatabase database, IBatchCollisionProcessor processor, UIStateManager uiStateManager) { _database = database; _processor = processor; _uiStateManager = uiStateManager; } /// /// 执行批处理任务 /// public async Task ExecuteBatchTaskAsync(BatchCollisionTask task, CancellationToken cancellationToken = default) { _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try { await UpdateTaskStatusAsync(task.Id, BatchTaskStatus.Running); var items = await _database.GetBatchTaskItemsAsync(task.Id); task.TotalItems = items.Count; var summaryReport = new BatchCollisionSummaryReport(); var startTime = DateTime.Now; // 按顺序执行每个任务项 foreach (var item in items.OrderBy(i => i.SequenceNumber)) { if (_cancellationTokenSource.Token.IsCancellationRequested) { await UpdateTaskStatusAsync(task.Id, BatchTaskStatus.Cancelled); break; } try { await ExecuteBatchTaskItemAsync(task, item, summaryReport); task.CompletedItems++; await UpdateTaskProgressAsync(task); TaskProgress?.Invoke(this, new BatchTaskProgressEventArgs { TaskId = task.Id, CompletedItems = task.CompletedItems, TotalItems = task.TotalItems, Percentage = (double)task.CompletedItems / task.TotalItems * 100 }); } catch (Exception ex) { LogManager.Error($"执行任务项失败 (RouteId: {item.RouteId}): {ex.Message}"); item.Status = BatchTaskItemStatus.Failed; item.ErrorMessage = ex.Message; task.FailedItems++; await _database.UpdateBatchTaskItemAsync(item); } } // 生成汇总报告 summaryReport.TotalDurationSeconds = (DateTime.Now - startTime).TotalSeconds; summaryReport.TotalPaths = items.Count; summaryReport.PathsFailed = task.FailedItems; // 从数据库加载统计信息(复用ClashDetectiveResults表) await LoadSummaryStatisticsAsync(task, summaryReport); task.SummaryReport = summaryReport; await _database.UpdateBatchTaskAsync(task); var finalStatus = _cancellationTokenSource.Token.IsCancellationRequested ? BatchTaskStatus.Cancelled : BatchTaskStatus.Completed; await UpdateTaskStatusAsync(task.Id, finalStatus); } catch (Exception ex) { LogManager.Error($"批处理任务执行失败 (TaskId: {task.Id}): {ex.Message}"); await UpdateTaskStatusAsync(task.Id, BatchTaskStatus.Failed); throw; } } /// /// 执行单个任务项 /// private async Task ExecuteBatchTaskItemAsync( BatchCollisionTask task, BatchTaskItem item, BatchCollisionSummaryReport summaryReport) { item.Status = BatchTaskItemStatus.Running; item.StartTime = DateTime.Now; await _database.UpdateBatchTaskItemAsync(item); var config = task.DefaultConfig.MergeWith(item.ConfigOverride); var pathRoute = await _database.GetPathRouteAsync(item.RouteId); if (pathRoute == null) { throw new InvalidOperationException($"路径不存在 (Id: {item.RouteId})"); } // 获取运动物体 ModelItem animatedObject = null; bool isVirtualObject = false; if (!string.IsNullOrEmpty(item.ObjectObjectId)) { // 从Navisworks文档中查找物体 animatedObject = FindModelItemById(item.ObjectObjectId); isVirtualObject = false; } else { // 使用虚拟物体 isVirtualObject = true; } // 在主线程执行Navisworks API调用(但无UI操作) await _uiStateManager.InvokeOnUIThreadAsync(() => { // 预计算动画帧和碰撞(纯计算) var frames = _processor.PrecomputeFrames( pathRoute, animatedObject, isVirtualObject, config.DetectionGapMeters, // 虚拟物体尺寸从配置获取 config.DetectionGapMeters, config.DetectionGapMeters, config.FrameRate, config.DurationSeconds, config.DetectionGapMeters, null // 暂不支持手动检测项 ); // 提取碰撞结果 var collisions = frames.SelectMany(f => f.Collisions).ToList(); // 创建并运行ClashDetective测试(纯计算,无UI操作) var testName = _processor.CreateAndRunClashDetectiveTest( collisions, config.DetectionGapMeters, pathRoute.Name, pathRoute.Id, animatedObject, isVirtualObject, config.FrameRate, config.DurationSeconds, config.DetectionGapMeters, config.DetectionGapMeters, config.DetectionGapMeters ); // 保存测试名称到任务项 item.ClashDetectiveTestName = testName; }); item.Status = BatchTaskItemStatus.Completed; item.EndTime = DateTime.Now; await _database.UpdateBatchTaskItemAsync(item); ItemCompleted?.Invoke(this, new BatchTaskItemCompletedEventArgs { TaskId = task.Id, TaskItemId = item.Id, PathRouteName = item.PathRouteName, Success = true }); } /// /// 从数据库加载汇总统计信息(复用ClashDetectiveResults表) /// private async Task LoadSummaryStatisticsAsync(BatchCollisionTask task, BatchCollisionSummaryReport summaryReport) { var items = await _database.GetBatchTaskItemsAsync(task.Id); foreach (var item in items.Where(i => i.Status == BatchTaskItemStatus.Completed)) { if (!string.IsNullOrEmpty(item.ClashDetectiveTestName)) { // 从ClashDetectiveResults表加载结果 var result = await _database.GetClashDetectiveResultByTestNameAsync(item.ClashDetectiveTestName); if (result != null) { summaryReport.TotalCollisions += result.CollisionCount; summaryReport.PathSummaries.Add(new PathCollisionSummary { RouteId = item.RouteId, PathRouteName = item.PathRouteName, CollisionCount = result.CollisionCount, TestName = result.TestName, ExecutionDurationSeconds = (item.EndTime.Value - item.StartTime.Value).TotalSeconds }); } } } summaryReport.PathsCollided = summaryReport.PathSummaries.Count(s => s.CollisionCount > 0); summaryReport.PathsNoCollision = summaryReport.PathSummaries.Count(s => s.CollisionCount == 0); } public void Cancel() { _cancellationTokenSource?.Cancel(); } private async Task UpdateTaskStatusAsync(int taskId, BatchTaskStatus status) { var task = await _database.GetBatchTaskAsync(taskId); if (status == BatchTaskStatus.Running) { task.StartTime = DateTime.Now; } else if (status == BatchTaskStatus.Completed || status == BatchTaskStatus.Failed || status == BatchTaskStatus.Cancelled) { task.EndTime = DateTime.Now; } task.Status = status; task.LastModified = DateTime.Now; await _database.UpdateBatchTaskAsync(task); } private async Task UpdateTaskProgressAsync(BatchCollisionTask task) { task.LastModified = DateTime.Now; await _database.UpdateBatchTaskAsync(task); } } } ``` ### 4.4 管理器类 #### BatchTaskManager.cs ```csharp namespace NavisworksTransport.Core { public class BatchTaskManager { private readonly PathDatabase _database; private readonly BatchCollisionExecutor _executor; private readonly CommandManager _commandManager; private readonly Queue _taskQueue; private readonly object _queueLock = new object(); private BatchCollisionTask _currentTask; public event EventHandler TaskCreated; public event EventHandler TaskStarted; public event EventHandler TaskCompleted; public event EventHandler TaskProgress; public bool IsExecuting => _currentTask != null; public int QueueCount => _taskQueue.Count; public BatchTaskManager( PathDatabase database, BatchCollisionExecutor executor, CommandManager commandManager) { _database = database; _executor = executor; _commandManager = commandManager; _taskQueue = new Queue(); _executor.TaskProgress += (s, e) => TaskProgress?.Invoke(s, e); } public async Task CreateBatchTaskAsync( string name, string description, List itemConfigs, CollisionDetectionConfig defaultConfig = null) { var task = new BatchCollisionTask { Name = name, Description = description, CreatedTime = DateTime.Now, CreatedBy = Environment.UserName, Status = BatchTaskStatus.Pending, DefaultConfig = defaultConfig ?? GetDefaultConfig(), LastModified = DateTime.Now }; var taskId = await _database.CreateBatchTaskAsync(task); task.Id = taskId; var sequence = 0; foreach (var itemConfig in itemConfigs) { var item = new BatchTaskItem { BatchTaskId = taskId, RouteId = itemConfig.RouteId, PathRouteName = itemConfig.PathRouteName, SequenceNumber = sequence++, Status = BatchTaskItemStatus.Pending, ConfigOverride = itemConfig.ConfigOverride, ObjectObjectId = itemConfig.ObjectObjectId, ObjectObjectName = itemConfig.ObjectObjectName, DetectionItems = itemConfig.DetectionItems }; await _database.CreateBatchTaskItemAsync(item); } task.TotalItems = itemConfigs.Count; await _database.UpdateBatchTaskAsync(task); TaskCreated?.Invoke(this, new BatchTaskEventArgs { Task = task }); return taskId; } public void EnqueueTask(int taskId) { lock (_queueLock) { _taskQueue.Enqueue(taskId); } if (!IsExecuting) { Task.Run(() => ProcessQueueAsync()); } } private async Task ProcessQueueAsync() { while (true) { int taskId; lock (_queueLock) { if (_taskQueue.Count == 0) { break; } taskId = _taskQueue.Dequeue(); } try { var task = await _database.GetBatchTaskAsync(taskId); _currentTask = task; TaskStarted?.Invoke(this, new BatchTaskEventArgs { Task = task }); await _executor.ExecuteBatchTaskAsync(task); TaskCompleted?.Invoke(this, new BatchTaskEventArgs { Task = task }); } catch (Exception ex) { LogManager.Error($"队列任务执行失败 (TaskId: {taskId}): {ex.Message}"); } finally { _currentTask = null; } } } public void CancelCurrentTask() { _executor.Cancel(); } public async Task> GetBatchTasksAsync( BatchTaskStatus? statusFilter = null, int limit = 50, int offset = 0) { return await _database.GetBatchTasksAsync(statusFilter, limit, offset); } public async Task GetBatchTaskAsync(int taskId) { return await _database.GetBatchTaskAsync(taskId); } public async Task DeleteBatchTaskAsync(int taskId) { await _database.DeleteBatchTaskAsync(taskId); } private CollisionDetectionConfig GetDefaultConfig() { var systemConfig = ConfigManager.Instance.Current; return new CollisionDetectionConfig { DetectionGapMeters = systemConfig.AnimationConfig.DetectionGapMeters, FrameRate = systemConfig.AnimationConfig.FrameRate, DurationSeconds = systemConfig.AnimationConfig.DurationSeconds }; } } } ``` ### 4.5 汇总报告生成器(新开发) #### BatchCollisionSummaryReportGenerator.cs ```csharp namespace NavisworksTransport.Core.Collision { public class BatchCollisionSummaryReportGenerator { private readonly PathDatabase _database; public BatchCollisionSummaryReportGenerator(PathDatabase database) { _database = database; } /// /// 生成汇总报告(HTML格式) /// public async Task GenerateSummaryReportHtmlAsync(int taskId) { var task = await _database.GetBatchTaskAsync(taskId); if (task == null) throw new ArgumentException($"任务不存在 (Id: {taskId})"); var items = await _database.GetBatchTaskItemsAsync(taskId); var sb = new StringBuilder(); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine("批处理碰撞检测汇总报告"); sb.AppendLine(""); // 任务信息 sb.AppendLine("

批处理碰撞检测汇总报告

"); sb.AppendLine("

任务信息

"); sb.AppendLine(""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine("
任务名称{task.Name}
任务描述{task.Description ?? "-"}
创建时间{task.CreatedTime:yyyy-MM-dd HH:mm:ss}
开始时间{task.StartTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-"}
结束时间{task.EndTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-"}
状态{task.Status}
总路径数{task.TotalItems}
已完成{task.CompletedItems}
失败{task.FailedItems}
"); // 汇总统计 if (task.SummaryReport != null) { sb.AppendLine("

汇总统计

"); sb.AppendLine("
"); sb.AppendLine("

总碰撞数:" + task.SummaryReport.TotalCollisions + "

"); sb.AppendLine("

有碰撞的路径:" + task.SummaryReport.PathsCollided + "

"); sb.AppendLine("

无碰撞的路径:" + task.SummaryReport.PathsNoCollision + "

"); sb.AppendLine("

执行总时长:" + task.SummaryReport.TotalDurationSeconds.ToString("F2") + " 秒

"); sb.AppendLine("
"); } // 路径详情 sb.AppendLine("

路径详情

"); sb.AppendLine(""); sb.AppendLine(""); foreach (var item in items.OrderBy(i => i.SequenceNumber)) { var duration = item.StartTime.HasValue && item.EndTime.HasValue ? (item.EndTime.Value - item.StartTime.Value).TotalSeconds : 0; var collisionCount = 0; if (!string.IsNullOrEmpty(item.ClashDetectiveTestName)) { var result = await _database.GetClashDetectiveResultByTestNameAsync(item.ClashDetectiveTestName); if (result != null) { collisionCount = result.CollisionCount; } } sb.AppendLine(""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine($""); sb.AppendLine(""); } sb.AppendLine("
序号路径名称状态碰撞数测试名称执行时长操作
{item.SequenceNumber + 1}{item.PathRouteName}{item.Status}{collisionCount}{item.ClashDetectiveTestName ?? "-"}{duration:F2} 秒{(!string.IsNullOrEmpty(item.ClashDetectiveTestName) ? $"查看报告" : "-")}
"); sb.AppendLine(""); return sb.ToString(); } } } ``` --- ## 五、报告系统 ### 5.1 单独报告(复用) 批处理任务的每个路径完成碰撞检测后,其结果已保存到 `ClashDetectiveResults` 表。查看单独报告时,直接复用现有的报告系统: ```csharp // 复用 GenerateCollisionReportCommand var reportCommand = new GenerateCollisionReportCommand(); reportCommand.SetTestName(item.ClashDetectiveTestName); await reportCommand.ExecuteAsync(); // 复用 CollisionReportDialog CollisionReportDialog.ShowReportModal(reportResult); ``` ### 5.2 汇总报告(新开发) 汇总报告由 `BatchCollisionSummaryReportGenerator` 生成,包含: - 任务信息(名称、描述、时间、状态) - 汇总统计(总碰撞数、有/无碰撞路径数、执行时长) - 路径详情列表(每条路径的状态、碰撞数、测试名称、执行时长、查看报告链接) --- ## 六、实施步骤 ### Phase 1: 数据库扩展 1. 在 `PathDatabase.cs` 中添加批处理表创建逻辑 2. 实现批处理任务和任务项的CRUD方法 3. 添加JSON序列化/反序列化辅助方法 ### Phase 2: 数据模型创建 1. 创建 `BatchCollisionTask.cs` 2. 创建 `BatchTaskItem.cs` 3. 创建 `CollisionDetectionConfig.cs` 4. 创建 `BatchCollisionSummaryReport.cs` ### Phase 3: 纯计算接口实现(解耦UI) 1. 创建 `IBatchCollisionProcessor.cs` 接口 2. 创建 `BatchCollisionProcessor.cs` 实现类 3. 从 `PathAnimationManager` 和 `ClashDetectiveIntegration` 提取核心计算逻辑 4. 确保完全不涉及UI操作(鼠标光标、视图重绘、DispatcherTimer) ### Phase 4: 执行器实现 1. 创建 `BatchCollisionExecutor.cs`(复用 `IBatchCollisionProcessor`) 2. 实现任务执行逻辑(主线程COM API调用,但无UI操作) 3. 从数据库加载汇总统计信息(复用 `ClashDetectiveResults` 表) ### Phase 5: 任务管理器实现 1. 创建 `BatchTaskManager.cs`(队列管理) 2. 实现任务创建、查询、删除 ### Phase 6: 汇总报告生成器实现(新开发) 1. 创建 `BatchCollisionSummaryReportGenerator.cs` 2. 生成汇总报告(HTML格式),包含任务信息、汇总统计、路径详情 ### Phase 7: Command实现 1. 创建 `CreateBatchTaskCommand.cs` 2. 创建 `ExecuteBatchTaskCommand.cs` 3. 创建 `DeleteBatchTaskCommand.cs` ### Phase 8: UI实现 1. 创建 `BatchTaskViewModel.cs` 2. 创建批处理任务管理页面 3. 创建创建任务对话框 4. 创建汇总报告查看对话框(新开发) 5. 单独报告复用 `GenerateCollisionReportCommand` 和 `CollisionReportDialog` ### Phase 9: 测试和优化 1. 单元测试和集成测试 2. 性能优化和错误处理完善 --- ## 七、文件清单 ### 新增文件 src/Core/Models/BatchCollisionTask.cs src/Core/Models/BatchTaskItem.cs src/Core/Models/CollisionDetectionConfig.cs src/Core/Models/BatchCollisionSummaryReport.cs src/Core/Collision/IBatchCollisionProcessor.cs src/Core/Collision/BatchCollisionProcessor.cs src/Core/Collision/BatchCollisionExecutor.cs src/Core/Collision/BatchCollisionSummaryReportGenerator.cs src/Core/BatchTaskManager.cs src/Commands/CreateBatchTaskCommand.cs src/Commands/ExecuteBatchTaskCommand.cs src/Commands/DeleteBatchTaskCommand.cs src/UI/WPF/ViewModels/BatchTaskViewModel.cs src/UI/WPF/Views/BatchTaskView.xaml src/UI/WPF/Views/BatchTaskView.xaml.cs src/UI/WPF/Views/CreateBatchTaskDialog.xaml src/UI/WPF/Views/CreateBatchTaskDialog.xaml.cs src/UI/WPF/Views/BatchSummaryReportDialog.xaml src/UI/WPF/Views/BatchSummaryReportDialog.xaml.cs ### 修改文件 src/Core/PathDatabase.cs (新增批处理相关方法) src/Core/MainPlugin.cs (注册BatchTaskManager) src/UI/WPF/ViewModels/LogisticsControlViewModel.cs (集成批处理页面) --- ## 八、关键技术要点 ### 8.1 COM API线程安全 ```csharp await _uiStateManager.InvokeOnUIThreadAsync(() => { // 所有Navisworks API调用(但无UI操作) var frames = _processor.PrecomputeFrames(...); var testName = _processor.CreateAndRunClashDetectiveTest(...); }); ``` ### 8.2 复用现有数据库结构 - 批处理任务项通过 `ClashDetectiveTestName` 关联到现有的 `ClashDetectiveResults` 表 - 单独报告直接复用 `GenerateCollisionReportCommand` 和 `CollisionReportDialog` ### 8.3 配置合并 ```csharp var config = task.DefaultConfig.MergeWith(item.ConfigOverride); ``` ### 8.4 进度更新和事件通知 ```csharp TaskProgress?.Invoke(this, new BatchTaskProgressEventArgs { ... }); ``` --- ## 九、现有动画检测与UI绑定分析 ### 9.1 UI绑定点分析 现有动画检测流程中,**确实存在多处UI绑定**: | 绑定位置 | 代码位置 | UI元素 | 用途 | |---------|---------|--------|------| | **鼠标光标** | `PathAnimationManager.cs:1396-1418` | `System.Windows.Input.Mouse.OverrideCursor` | 显示等待光标 | | **DispatcherTimer** | `PathAnimationManager.cs:126,199` | `DispatcherTimer` | 动画播放定时器 | | **视图重绘** | `PathAnimationManager.cs:1195` | `NavisApplication.ActiveDocument.ActiveView.RequestDelayedRedraw()` | 3D视图实时更新 | | **状态通知** | 多处 | 通过事件通知UI更新 | 进度条、状态显示 | ### 9.2 能否支持后台批处理? 答案:可以,但需要解耦UI绑定** #### ✅ **可以复用的部分**(无UI绑定) - `PrecomputeAnimationFrames()` - 纯计算逻辑 - `ClashDetectiveIntegration.CreateAllAnimationCollisionTests()` - 核心碰撞检测 - `ClashDetectiveIntegration.SaveClashDetectiveResultToDatabase()` - 数据持久化 #### ❌ **需要解耦的部分**(有UI绑定) 1. **DispatcherTimer** - 动画播放定时器(批处理不需要播放) 2. **鼠标光标** - `Mouse.OverrideCursor`(批处理不需要) 3. **视图重绘** - `RequestDelayedRedraw()`(批处理不需要) 4. **状态通知** - UI进度更新(批处理需要,但方式不同) ### 9.3 解决方案 提取纯计算接口 核心思路:将动画检测的核心计算逻辑提取为独立接口,批处理只调用计算部分,不调用UI相关部分。 --- ## 十、总结 本方案基于**方案一(提取纯计算接口)**,并**复用现有的碰撞检测结果、数据库结构和报告系统**,实现批处理碰撞检测功能。 ### 核心优势 1. **解耦清晰**:批处理和UI完全分离,互不影响 2. **易于维护**:UI修改不会影响批处理逻辑 3. **可测试性**:纯计算接口更容易进行单元测试 4. **扩展性好**:未来可以支持其他批处理场景 5. **复用性强**:最大化复用现有系统,减少重复开发 ### 关键技术 - 线程安全:使用 `UIStateManager` 确保COM API在主线程执行 - 复用数据库:通过 `ClashDetectiveTestName` 关联到现有 `ClashDetectiveResults` 表 - 复用报告:单独报告直接复用现有报告系统 - 配置合并:默认配置 + 覆盖配置 ---