# 批处理碰撞检测功能实施方案

**文档创建日期**: 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<BatchTaskItem> Items { get; set; } = new List<BatchTaskItem>();

    }



    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<string> DetectionItems { get; set; } = new List<string>();
    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;
    
    /// <summary>
    /// 合并配置（覆盖配置优先）
    /// </summary>
    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
{
    /// <summary>
    /// 批处理碰撞检测纯计算接口
    /// 关键特性：完全不涉及UI操作（鼠标光标、视图重绘、DispatcherTimer）
    /// </summary>
    public interface IBatchCollisionProcessor
    {
        /// <summary>
        /// 预计算动画帧和碰撞（纯计算，无UI操作）
        /// </summary>
        List<AnimationFrame> PrecomputeFrames(
            PathRoute route,
            ModelItem animatedObject,
            bool isVirtualObject,
            double virtualObjectLength,
            double virtualObjectWidth,
            double virtualObjectHeight,
            int frameRate,
            double duration,
            double detectionGap,
            List<ModelItem> manualDetectionTargets = null);
        
        /// <summary>
        /// 创建并运行ClashDetective测试（纯计算，无UI操作）
        /// 返回测试名称，用于关联到ClashDetectiveResults表
        /// </summary>
        string CreateAndRunClashDetectiveTest(
            List<CollisionResult> 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<BatchTaskProgressEventArgs> TaskProgress;
        public event EventHandler<BatchTaskItemCompletedEventArgs> ItemCompleted;

        public BatchCollisionExecutor(
            PathDatabase database,
            IBatchCollisionProcessor processor,
            UIStateManager uiStateManager)
        {
            _database = database;
            _processor = processor;
            _uiStateManager = uiStateManager;
        }

        /// <summary>
        /// 执行批处理任务
        /// </summary>
        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;
            }
        }

        /// <summary>
        /// 执行单个任务项
        /// </summary>
        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
            });
        }

        /// <summary>
        /// 从数据库加载汇总统计信息（复用ClashDetectiveResults表）
        /// </summary>
        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<int> _taskQueue;
        private readonly object _queueLock = new object();
        private BatchCollisionTask _currentTask;

        public event EventHandler<BatchTaskEventArgs> TaskCreated;
        public event EventHandler<BatchTaskEventArgs> TaskStarted;
        public event EventHandler<BatchTaskEventArgs> TaskCompleted;
        public event EventHandler<BatchTaskProgressEventArgs> 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<int>();
            
            _executor.TaskProgress += (s, e) => TaskProgress?.Invoke(s, e);
        }

        public async Task<int> CreateBatchTaskAsync(
            string name, 
            string description,
            List<BatchTaskItemConfig> 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<List<BatchCollisionTask>> GetBatchTasksAsync(
            BatchTaskStatus? statusFilter = null,
            int limit = 50,
            int offset = 0)
        {
            return await _database.GetBatchTasksAsync(statusFilter, limit, offset);
        }

        public async Task<BatchCollisionTask> 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;
        }

        /// <summary>
        /// 生成汇总报告（HTML格式）
        /// </summary>
        public async Task<string> 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("<!DOCTYPE html>");
            sb.AppendLine("<html><head><meta charset='utf-8'>");
            sb.AppendLine("<title>批处理碰撞检测汇总报告</title>");
            sb.AppendLine("<style>");
            sb.AppendLine("body { font-family: Arial, sans-serif; margin: 20px; }");
            sb.AppendLine("h1 { color: #333; }");
            sb.AppendLine("h2 { color: #666; border-bottom: 2px solid #ddd; padding-bottom: 10px; }");
            sb.AppendLine("table { border-collapse: collapse; width: 100%; margin: 20px 0; }");
            sb.AppendLine("th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }");
            sb.AppendLine("th { background-color: #4CAF50; color: white; }");
            sb.AppendLine("tr:nth-child(even) { background-color: #f2f2f2; }");
            sb.AppendLine(".status-completed { color: green; font-weight: bold; }");
            sb.AppendLine(".status-failed { color: red; font-weight: bold; }");
            sb.AppendLine(".status-pending { color: orange; font-weight: bold; }");
            sb.AppendLine(".status-running { color: blue; font-weight: bold; }");
            sb.AppendLine(".summary-box { background-color: #e7f3fe; border-left: 6px solid #2196F3; padding: 15px; margin: 20px 0; }");
            sb.AppendLine("</style></head><body>");
            
            // 任务信息
            sb.AppendLine("<h1>批处理碰撞检测汇总报告</h1>");
            sb.AppendLine("<h2>任务信息</h2>");
            sb.AppendLine("<table>");
            sb.AppendLine($"<tr><th>任务名称</th><td>{task.Name}</td></tr>");
            sb.AppendLine($"<tr><th>任务描述</th><td>{task.Description ?? "-"}</td></tr>");
            sb.AppendLine($"<tr><th>创建时间</th><td>{task.CreatedTime:yyyy-MM-dd HH:mm:ss}</td></tr>");
            sb.AppendLine($"<tr><th>开始时间</th><td>{task.StartTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-"}</td></tr>");
            sb.AppendLine($"<tr><th>结束时间</th><td>{task.EndTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-"}</td></tr>");
            sb.AppendLine($"<tr><th>状态</th><td class='status-{task.Status.ToString().ToLower()}'>{task.Status}</td></tr>");
            sb.AppendLine($"<tr><th>总路径数</th><td>{task.TotalItems}</td></tr>");
            sb.AppendLine($"<tr><th>已完成</th><td>{task.CompletedItems}</td></tr>");
            sb.AppendLine($"<tr><th>失败</th><td>{task.FailedItems}</td></tr>");
            sb.AppendLine("</table>");
            
            // 汇总统计
            if (task.SummaryReport != null)
            {
                sb.AppendLine("<h2>汇总统计</h2>");
                sb.AppendLine("<div class='summary-box'>");
                sb.AppendLine("<p><strong>总碰撞数：</strong>" + task.SummaryReport.TotalCollisions + "</p>");
                sb.AppendLine("<p><strong>有碰撞的路径：</strong>" + task.SummaryReport.PathsCollided + "</p>");
                sb.AppendLine("<p><strong>无碰撞的路径：</strong>" + task.SummaryReport.PathsNoCollision + "</p>");
                sb.AppendLine("<p><strong>执行总时长：</strong>" + task.SummaryReport.TotalDurationSeconds.ToString("F2") + " 秒</p>");
                sb.AppendLine("</div>");
            }
            
            // 路径详情
            sb.AppendLine("<h2>路径详情</h2>");
            sb.AppendLine("<table>");
            sb.AppendLine("<tr><th>序号</th><th>路径名称</th><th>状态</th><th>碰撞数</th><th>测试名称</th><th>执行时长</th><th>操作</th></tr>");
            
            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("<tr>");
                sb.AppendLine($"<td>{item.SequenceNumber + 1}</td>");
                sb.AppendLine($"<td>{item.PathRouteName}</td>");
                sb.AppendLine($"<td class='status-{item.Status.ToString().ToLower()}'>{item.Status}</td>");
                sb.AppendLine($"<td>{collisionCount}</td>");
                sb.AppendLine($"<td>{item.ClashDetectiveTestName ?? "-"}</td>");
                sb.AppendLine($"<td>{duration:F2} 秒</td>");
                sb.AppendLine($"<td>{(!string.IsNullOrEmpty(item.ClashDetectiveTestName) ? $"<a href='#' onclick='viewPathReport(\"{item.ClashDetectiveTestName}\")'>查看报告</a>" : "-")}</td>");
                sb.AppendLine("</tr>");
            }
            
            sb.AppendLine("</table>");
            sb.AppendLine("</body></html>");
            
            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` 表
- 复用报告：单独报告直接复用现有报告系统
- 配置合并：默认配置 + 覆盖配置

---
