简化批处理,变成队列
This commit is contained in:
parent
6dc266e526
commit
b4a73e2081
@ -112,14 +112,15 @@
|
||||
<Compile Include="src\Core\PathDatabase.cs" />
|
||||
<Compile Include="src\Core\PathAnalysisService.cs" />
|
||||
<Compile Include="src\Core\PathPlanningModels.cs" />
|
||||
<Compile Include="src\Core\Models\BatchQueueItem.cs" />
|
||||
<Compile Include="src\Core\Models\BatchCollisionTask.cs" />
|
||||
<Compile Include="src\Core\Models\BatchTaskItem.cs" />
|
||||
<Compile Include="src\Core\Models\BatchTaskUIModels.cs" />
|
||||
<Compile Include="src\Core\Models\CollisionDetectionConfig.cs" />
|
||||
<Compile Include="src\Core\Models\BatchCollisionSummaryReport.cs" />
|
||||
<Compile Include="src\Core\PathCurveEngine.cs" />
|
||||
<Compile Include="src\Core\GridVisualization.cs" />
|
||||
<Compile Include="src\Core\BatchTaskManager.cs" />
|
||||
<Compile Include="src\Core\BatchQueueManager.cs" />
|
||||
<!-- Core - Events and Interfaces -->
|
||||
<Compile Include="src\Core\IPathPlanningManagerEvents.cs" />
|
||||
<Compile Include="src\Core\PathPlanningManagerEventArgs.cs" />
|
||||
@ -279,6 +280,8 @@
|
||||
<Compile Include="src\UI\WPF\Converters\IndexConverter.cs" />
|
||||
<Compile Include="src\UI\WPF\Converters\CountToVisibilityConverter.cs" />
|
||||
<Compile Include="src\UI\WPF\Converters\PathTypeConverter.cs" />
|
||||
<Compile Include="src\UI\WPF\Converters\BatchQueueStatusConverter.cs" />
|
||||
<Compile Include="src\UI\WPF\Converters\BatchQueueStatusHelper.cs" />
|
||||
<Compile Include="src\UI\WPF\Models\LogisticsModel.cs" />
|
||||
<Compile Include="src\UI\WPF\Models\PathRouteViewModel.cs" />
|
||||
<Compile Include="src\UI\WPF\Models\SplitPreviewItem.cs" />
|
||||
@ -392,6 +395,22 @@
|
||||
<DependentUpon>CreateBatchTaskDialog.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Page Include="src\UI\WPF\Views\PathSelectionDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Compile Include="src\UI\WPF\Views\PathSelectionDialog.xaml.cs">
|
||||
<DependentUpon>PathSelectionDialog.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Page Include="src\UI\WPF\Views\PathConfigDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Compile Include="src\UI\WPF\Views\PathConfigDialog.xaml.cs">
|
||||
<DependentUpon>PathConfigDialog.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<!-- Shared Resource Dictionary -->
|
||||
<Page Include="src\UI\WPF\Resources\NavisworksStyles.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
|
||||
@ -196,8 +196,8 @@ namespace NavisworksTransport.Commands
|
||||
_commandQueue.Enqueue(request);
|
||||
OnQueueStatusChanged();
|
||||
|
||||
// 启动处理队列
|
||||
_ = Task.Run(ProcessQueueAsync);
|
||||
// 启动处理队列(直接在主线程执行,避免后台线程导致的Navisworks Native对象生命周期问题)
|
||||
ProcessQueueAsync();
|
||||
|
||||
LogManager.Info($"命令已加入队列: {command.DisplayName} (优先级: {priority})");
|
||||
|
||||
|
||||
392
src/Core/BatchQueueManager.cs
Normal file
392
src/Core/BatchQueueManager.cs
Normal file
@ -0,0 +1,392 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisworksTransport.Core.Animation;
|
||||
using NavisworksTransport.Core.Collision;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 批处理队列管理器 - 简化版,只维护FIFO队列
|
||||
/// </summary>
|
||||
public class BatchQueueManager
|
||||
{
|
||||
private static BatchQueueManager _instance;
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
private PathDatabase _database;
|
||||
private BatchCollisionExecutor _executor;
|
||||
private readonly Queue<int> _queue;
|
||||
private readonly object _queueLock = new object();
|
||||
private BatchQueueItem _currentItem;
|
||||
|
||||
public event EventHandler<BatchQueueItemEventArgs> ItemAdded;
|
||||
public event EventHandler<BatchQueueItemEventArgs> ItemStarted;
|
||||
public event EventHandler<BatchQueueItemEventArgs> ItemCompleted;
|
||||
public event EventHandler<BatchQueueProgressEventArgs> ItemProgress;
|
||||
|
||||
public bool IsExecuting => _currentItem != null;
|
||||
public int QueueCount => _queue.Count;
|
||||
|
||||
public static BatchQueueManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new BatchQueueManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private BatchQueueManager()
|
||||
{
|
||||
// 延迟初始化,等待 PathDatabase 就绪
|
||||
_database = null;
|
||||
_executor = null;
|
||||
_queue = new Queue<int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置PathPlanningManager(用于获取数据库)
|
||||
/// </summary>
|
||||
public void SetPathPlanningManager(PathPlanningManager pathPlanningManager)
|
||||
{
|
||||
_database = pathPlanningManager?.GetPathDatabase();
|
||||
LogManager.Info("BatchQueueManager已设置PathPlanningManager");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保 Executor 已初始化
|
||||
/// </summary>
|
||||
private void EnsureExecutorInitialized()
|
||||
{
|
||||
if (_executor == null)
|
||||
{
|
||||
var pathManager = PathPlanningManager.Instance;
|
||||
_database = pathManager?.GetPathDatabase();
|
||||
if (_database == null)
|
||||
{
|
||||
throw new InvalidOperationException("PathDatabase未初始化,请确保文档已加载");
|
||||
}
|
||||
|
||||
_executor = new BatchCollisionExecutor(
|
||||
_database,
|
||||
new BatchCollisionProcessor(),
|
||||
UIStateManager.Instance
|
||||
);
|
||||
|
||||
// 转换事件参数类型
|
||||
_executor.TaskProgress += (s, e) =>
|
||||
{
|
||||
ItemProgress?.Invoke(s, new BatchQueueProgressEventArgs
|
||||
{
|
||||
ItemId = e.TaskId,
|
||||
CompletedItems = e.CompletedItems,
|
||||
TotalItems = e.TotalItems,
|
||||
Percentage = e.Percentage
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加队列项
|
||||
/// </summary>
|
||||
public async Task<int> AddQueueItemAsync(BatchQueueItem item)
|
||||
{
|
||||
if (_database == null)
|
||||
{
|
||||
throw new InvalidOperationException("PathDatabase未初始化");
|
||||
}
|
||||
|
||||
var itemId = await _database.CreateBatchQueueItemAsync(item);
|
||||
item.Id = itemId;
|
||||
|
||||
lock (_queueLock)
|
||||
{
|
||||
_queue.Enqueue(itemId);
|
||||
}
|
||||
|
||||
ItemAdded?.Invoke(this, new BatchQueueItemEventArgs { Item = item });
|
||||
|
||||
LogManager.Info($"[批处理队列] 已添加队列项: {item.PathRouteName}, ID: {itemId}");
|
||||
return itemId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理队列(公共方法,供外部调用)
|
||||
/// </summary>
|
||||
public async Task ProcessQueueAsync()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int itemId;
|
||||
lock (_queueLock)
|
||||
{
|
||||
if (_queue.Count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
itemId = _queue.Dequeue();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
EnsureExecutorInitialized();
|
||||
|
||||
var item = await _database.GetBatchQueueItemAsync(itemId);
|
||||
_currentItem = item;
|
||||
|
||||
ItemStarted?.Invoke(this, new BatchQueueItemEventArgs { Item = item });
|
||||
|
||||
await ExecuteQueueItemAsync(item);
|
||||
|
||||
ItemCompleted?.Invoke(this, new BatchQueueItemEventArgs { Item = item });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"队列项执行失败 (Id: {itemId}): {ex.Message}");
|
||||
|
||||
// 更新状态为失败
|
||||
if (_database != null)
|
||||
{
|
||||
var item = await _database.GetBatchQueueItemAsync(itemId);
|
||||
if (item != null)
|
||||
{
|
||||
item.Status = BatchQueueStatus.Failed;
|
||||
item.ErrorMessage = ex.Message;
|
||||
item.EndTime = DateTime.Now;
|
||||
await _database.UpdateBatchQueueItemAsync(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行单个队列项
|
||||
/// </summary>
|
||||
private async Task ExecuteQueueItemAsync(BatchQueueItem item)
|
||||
{
|
||||
item.Status = BatchQueueStatus.Running;
|
||||
item.StartTime = DateTime.Now;
|
||||
await _database.UpdateBatchQueueItemAsync(item);
|
||||
|
||||
try
|
||||
{
|
||||
var pathRoute = await _database.GetPathRouteAsync(item.PathRouteId);
|
||||
if (pathRoute == null)
|
||||
{
|
||||
throw new InvalidOperationException($"路径不存在 (Id: {item.PathRouteId})");
|
||||
}
|
||||
|
||||
// 获取运动物体
|
||||
ModelItem animatedObject = null;
|
||||
bool isVirtualVehicle = item.IsVirtualVehicle;
|
||||
if (!string.IsNullOrEmpty(item.VehicleObjectId))
|
||||
{
|
||||
animatedObject = FindModelItemById(item.VehicleObjectId);
|
||||
isVirtualVehicle = false;
|
||||
}
|
||||
else if (isVirtualVehicle)
|
||||
{
|
||||
// 🔥 使用现有的虚拟车辆,不创建和清理
|
||||
// ShowVirtualVehicle 会显示虚拟车辆并更新尺寸(如果已存在)
|
||||
VirtualVehicleManager.Instance.ShowVirtualVehicle(
|
||||
item.VirtualVehicleLength,
|
||||
item.VirtualVehicleWidth,
|
||||
item.VirtualVehicleHeight
|
||||
);
|
||||
|
||||
animatedObject = VirtualVehicleManager.Instance.CurrentVirtualVehicle;
|
||||
|
||||
if (animatedObject == null)
|
||||
{
|
||||
throw new InvalidOperationException("虚拟车辆显示失败");
|
||||
}
|
||||
|
||||
LogManager.Info($"[批处理] 使用虚拟车辆: {animatedObject.DisplayName}");
|
||||
}
|
||||
|
||||
// 初始化碰撞检测缓存
|
||||
LogManager.Info("[批处理] 初始化碰撞检测缓存...");
|
||||
ClashDetectiveIntegration.InitializeCollisionDetectionCache(animatedObject);
|
||||
|
||||
// 在主线程执行Navisworks API调用
|
||||
var frames = await UIStateManager.Instance.ExecuteUIUpdateAsync(() =>
|
||||
{
|
||||
// 🔥 重要:预计算前必须将车辆移动到起点位置,确保变换状态与实际动画播放时一致
|
||||
var animationManager = PathAnimationManager.GetInstance();
|
||||
animationManager.MoveVehicleToPathStart(animatedObject, pathRoute.Points.Select(p => p.Position).ToList());
|
||||
|
||||
// 预计算动画帧和碰撞
|
||||
var config = new CollisionDetectionConfig
|
||||
{
|
||||
FrameRate = item.FrameRate,
|
||||
DurationSeconds = item.DurationSeconds,
|
||||
DetectionGapMeters = item.DetectionGapMeters,
|
||||
VirtualVehicleLength = item.VirtualVehicleLength,
|
||||
VirtualVehicleWidth = item.VirtualVehicleWidth,
|
||||
VirtualVehicleHeight = item.VirtualVehicleHeight,
|
||||
CollisionDetectionEnabled = true,
|
||||
ReportGenerationEnabled = true
|
||||
};
|
||||
|
||||
return _executor._processor.PrecomputeFrames(
|
||||
pathRoute,
|
||||
animatedObject,
|
||||
isVirtualVehicle,
|
||||
config.VirtualVehicleLength,
|
||||
config.VirtualVehicleWidth,
|
||||
config.VirtualVehicleHeight,
|
||||
config.FrameRate,
|
||||
config.DurationSeconds,
|
||||
config.DetectionGapMeters,
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
// 检查预计算是否成功
|
||||
if (frames == null || frames.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("预计算动画帧失败:未生成任何帧");
|
||||
}
|
||||
|
||||
// 提取碰撞结果
|
||||
var collisions = frames.SelectMany(f => f.Collisions).ToList();
|
||||
|
||||
// 创建并运行ClashDetective测试(直接在主线程执行,避免异步切换导致的Navisworks Native对象生命周期问题)
|
||||
var testName = _executor._processor.CreateAndRunClashDetectiveTest(
|
||||
collisions,
|
||||
item.DetectionGapMeters,
|
||||
pathRoute.Name,
|
||||
pathRoute.Id,
|
||||
animatedObject,
|
||||
isVirtualVehicle,
|
||||
item.FrameRate,
|
||||
item.DurationSeconds,
|
||||
item.VirtualVehicleLength,
|
||||
item.VirtualVehicleWidth,
|
||||
item.VirtualVehicleHeight
|
||||
);
|
||||
|
||||
// 🔥 使用 ClashDetective 确认的碰撞数,而不是预计算的碰撞数
|
||||
item.CollisionCount = ClashDetectiveIntegration.Instance.ClashDetectiveCollisionCount;
|
||||
|
||||
// 保存测试名称
|
||||
item.ClashDetectiveTestName = testName;
|
||||
|
||||
// 标记为完成
|
||||
item.Status = BatchQueueStatus.Completed;
|
||||
item.EndTime = DateTime.Now;
|
||||
await _database.UpdateBatchQueueItemAsync(item);
|
||||
|
||||
LogManager.Info($"[批处理队列] 队列项执行完成: {item.PathRouteName}, 碰撞数: {item.CollisionCount}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 标记为失败
|
||||
item.Status = BatchQueueStatus.Failed;
|
||||
item.ErrorMessage = ex.Message;
|
||||
item.EndTime = DateTime.Now;
|
||||
await _database.UpdateBatchQueueItemAsync(item);
|
||||
|
||||
LogManager.Error($"[批处理队列] 队列项执行失败: {item.PathRouteName}, 错误: {ex.Message}");
|
||||
throw; // 重新抛出异常,让上层处理
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消当前任务
|
||||
/// </summary>
|
||||
public void CancelCurrentItem()
|
||||
{
|
||||
EnsureExecutorInitialized();
|
||||
_executor.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取队列列表
|
||||
/// </summary>
|
||||
public async Task<List<BatchQueueItem>> GetQueueItemsAsync(
|
||||
BatchQueueStatus? statusFilter = null,
|
||||
int limit = 50,
|
||||
int offset = 0)
|
||||
{
|
||||
return await _database.GetBatchQueueItemsAsync(statusFilter, limit, offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个队列项
|
||||
/// </summary>
|
||||
public async Task<BatchQueueItem> GetQueueItemAsync(int itemId)
|
||||
{
|
||||
return await _database.GetBatchQueueItemAsync(itemId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除队列项
|
||||
/// </summary>
|
||||
public async Task DeleteQueueItemAsync(int itemId)
|
||||
{
|
||||
await _database.DeleteBatchQueueItemAsync(itemId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找模型项
|
||||
/// </summary>
|
||||
private ModelItem FindModelItemById(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
|
||||
if (doc == null) return null;
|
||||
|
||||
var model = doc.Models.RootItemDescendants.FirstOrDefault(
|
||||
item => item.ToString() == id
|
||||
);
|
||||
|
||||
return model;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"查找模型项失败 (Id: {id}): {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批处理队列项事件参数
|
||||
/// </summary>
|
||||
public class BatchQueueItemEventArgs : EventArgs
|
||||
{
|
||||
public BatchQueueItem Item { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批处理队列进度事件参数
|
||||
/// </summary>
|
||||
public class BatchQueueProgressEventArgs : EventArgs
|
||||
{
|
||||
public int ItemId { get; set; }
|
||||
public int CompletedItems { get; set; }
|
||||
public int TotalItems { get; set; }
|
||||
public double Percentage { get; set; }
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ namespace NavisworksTransport.Core.Collision
|
||||
public class BatchCollisionExecutor
|
||||
{
|
||||
private readonly PathDatabase _database;
|
||||
private readonly IBatchCollisionProcessor _processor;
|
||||
public readonly IBatchCollisionProcessor _processor;
|
||||
private readonly UIStateManager _uiStateManager;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
|
||||
@ -471,6 +471,276 @@ namespace NavisworksTransport
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行ClashDetective测试并保存到数据库(公共方法,供批处理和非批处理调用)
|
||||
/// </summary>
|
||||
/// <returns>碰撞分组、主测试、确认的碰撞数量</returns>
|
||||
public (ClashResultGroup collisionGroup, ClashTest addedMainTest, int confirmedCount) RunClashDetectiveTestsAndSaveToDatabase(
|
||||
List<CollisionResult> precomputedCollisions,
|
||||
double detectionGap,
|
||||
string pathName,
|
||||
string routeId,
|
||||
ModelItem animatedObject,
|
||||
bool isVirtualVehicle,
|
||||
int frameRate,
|
||||
double duration,
|
||||
double virtualVehicleLength,
|
||||
double virtualVehicleWidth,
|
||||
double virtualVehicleHeight,
|
||||
Progress progress = null)
|
||||
{
|
||||
LogManager.Info($"[ClashDetective] 开始运行碰撞检测并保存到数据库(容差: {detectionGap}米)");
|
||||
|
||||
// 过滤有效的碰撞
|
||||
var validCollisions = precomputedCollisions.Where(collision =>
|
||||
collision.HasPositionInfo &&
|
||||
IsModelItemValid(collision.Item1) &&
|
||||
IsModelItemValid(collision.Item2)
|
||||
).ToList();
|
||||
|
||||
if (validCollisions.Count == 0)
|
||||
{
|
||||
LogManager.Warning("[ClashDetective] 没有有效的碰撞可以创建测试");
|
||||
return (null, null, 0);
|
||||
}
|
||||
|
||||
LogManager.Info($"[ClashDetective] 有效碰撞数量: {validCollisions.Count}");
|
||||
|
||||
// 第一步:创建主测试(不包含选择集,纯容器)
|
||||
var mainTestName = $"碰撞检测_{DateTime.Now:MMdd_HHmmss}";
|
||||
_currentTestName = mainTestName;
|
||||
LogManager.Info($"[分组测试] 创建主测试: {mainTestName}");
|
||||
|
||||
// 创建主测试
|
||||
var mainTest = new ClashTest
|
||||
{
|
||||
DisplayName = mainTestName,
|
||||
TestType = ClashTestType.Hard,
|
||||
Tolerance = detectionGap,
|
||||
Guid = Guid.Empty,
|
||||
MergeComposites = true
|
||||
};
|
||||
|
||||
// 添加主测试到文档
|
||||
_documentClash.TestsData.TestsAddCopy(mainTest);
|
||||
LogManager.Info($"[分组测试] 主测试已添加到文档");
|
||||
|
||||
// 获取添加后的测试对象引用
|
||||
var addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest;
|
||||
if (addedMainTest == null)
|
||||
{
|
||||
LogManager.Error("[分组测试] 无法获取添加后的主测试对象");
|
||||
return (null, null, 0);
|
||||
}
|
||||
|
||||
// 第二步:创建分组并添加碰撞结果(应用智能去重)
|
||||
// 1. 分组:按碰撞对象对分组
|
||||
var groupedCollisions = validCollisions
|
||||
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
|
||||
.ToList();
|
||||
|
||||
LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对");
|
||||
|
||||
// 缓存去重后的碰撞结果(每组取第一个)
|
||||
lock (_resultsLock)
|
||||
{
|
||||
_deduplicatedCollisionResults.Clear();
|
||||
foreach (var group in groupedCollisions)
|
||||
{
|
||||
_deduplicatedCollisionResults.Add(group.First());
|
||||
}
|
||||
LogManager.Debug($"[去重缓存] 已缓存 {_deduplicatedCollisionResults.Count} 个去重后的碰撞结果");
|
||||
}
|
||||
|
||||
var collisionGroup = new ClashResultGroup
|
||||
{
|
||||
DisplayName = $"碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)"
|
||||
};
|
||||
|
||||
LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}");
|
||||
|
||||
int confirmedCount = 0;
|
||||
int skippedCount = 0;
|
||||
var doc = Application.ActiveDocument;
|
||||
|
||||
// 2. 遍历每一组
|
||||
for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++)
|
||||
{
|
||||
// 检查用户是否取消
|
||||
if (progress != null && progress.IsCanceled)
|
||||
{
|
||||
LogManager.Info($"[分组测试] 用户取消操作,已处理 {groupIndex}/{groupedCollisions.Count} 组");
|
||||
break;
|
||||
}
|
||||
|
||||
var group = groupedCollisions[groupIndex];
|
||||
|
||||
// 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞
|
||||
// 注意:Distance通常越小(或负值越大)表示碰撞越深,具体取决于计算方式
|
||||
// 这里假设Distance越小越严重
|
||||
var sortedCandidates = group.OrderBy(c => c.Distance).ToList();
|
||||
|
||||
bool pairConfirmed = false;
|
||||
|
||||
// 4. 验证即止:逐个检测候选帧
|
||||
for (int i = 0; i < sortedCandidates.Count; i++)
|
||||
{
|
||||
var candidate = sortedCandidates[i];
|
||||
|
||||
try
|
||||
{
|
||||
var testAnimatedObject = candidate.Item1;
|
||||
var modelItems = new ModelItemCollection { testAnimatedObject };
|
||||
var targetPosition = candidate.Item1Position;
|
||||
|
||||
var currentBounds = testAnimatedObject.BoundingBox();
|
||||
var currentPos = new Point3D(
|
||||
(currentBounds.Min.X + currentBounds.Max.X) / 2,
|
||||
(currentBounds.Min.Y + currentBounds.Max.Y) / 2,
|
||||
(currentBounds.Min.Z + currentBounds.Max.Z) / 2
|
||||
);
|
||||
|
||||
var offset = new Vector3D(
|
||||
targetPosition.X - currentPos.X,
|
||||
targetPosition.Y - currentPos.Y,
|
||||
targetPosition.Z - currentPos.Z
|
||||
);
|
||||
|
||||
var transform = Transform3D.CreateTranslation(offset);
|
||||
doc.Models.OverridePermanentTransform(modelItems, transform, false);
|
||||
|
||||
var tempTestName = $"临时验证_{confirmedCount + 1}_{i}_{DateTime.Now:HHmmss_fff}";
|
||||
var tempTest = new ClashTest
|
||||
{
|
||||
DisplayName = tempTestName,
|
||||
TestType = ClashTestType.Hard,
|
||||
Tolerance = detectionGap,
|
||||
Guid = Guid.Empty,
|
||||
MergeComposites = true
|
||||
};
|
||||
|
||||
var selectionA = new ModelItemCollection { candidate.Item1 };
|
||||
var selectionB = new ModelItemCollection { candidate.Item2 };
|
||||
tempTest.SelectionA.Selection.CopyFrom(selectionA);
|
||||
tempTest.SelectionB.Selection.CopyFrom(selectionB);
|
||||
|
||||
_documentClash.TestsData.TestsAddCopy(tempTest);
|
||||
var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
|
||||
|
||||
if (addedTempTest != null)
|
||||
{
|
||||
var copyTest = addedTempTest.CreateCopy() as ClashTest;
|
||||
copyTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
|
||||
copyTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
|
||||
_documentClash.TestsData.TestsEditTestFromCopy(addedTempTest, copyTest);
|
||||
|
||||
_documentClash.TestsData.TestsRunTest(addedTempTest);
|
||||
|
||||
var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
|
||||
if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0)
|
||||
{
|
||||
// !!!发现真实碰撞!!!
|
||||
pairConfirmed = true;
|
||||
confirmedCount++;
|
||||
skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量
|
||||
|
||||
int subResultIndex = 1;
|
||||
foreach (var child in refreshedTempTest.Children)
|
||||
{
|
||||
if (child is ClashResult result)
|
||||
{
|
||||
var copiedResult = result.CreateCopy() as ClashResult;
|
||||
copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID
|
||||
|
||||
// 设置唯一且有意义的碰撞名称
|
||||
// 先找到有意义的容器对象,再获取其名称
|
||||
var container1 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item1);
|
||||
var container2 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item2);
|
||||
var object1Name = ModelItemAnalysisHelper.GetSafeDisplayName(container1);
|
||||
var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(container2);
|
||||
|
||||
var timeStamp = DateTime.Now.ToString("HHmmss");
|
||||
copiedResult.DisplayName = $"物流碰撞#{confirmedCount:00}-{subResultIndex:00}_{timeStamp}: {object1Name} ↔ {object2Name}";
|
||||
|
||||
collisionGroup.Children.Add(copiedResult);
|
||||
subResultIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_documentClash.TestsData.TestsRemove(refreshedTempTest ?? addedTempTest);
|
||||
|
||||
if (pairConfirmed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception itemEx)
|
||||
{
|
||||
LogManager.Error($"[ClashDetective] 候选检测失败: {itemEx.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[分组测试] ClashDetective检测完成: 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点");
|
||||
|
||||
// 第三步:处理碰撞结果
|
||||
// 🔥 重要:ClashDetective 返回的是几何体级别的碰撞结果
|
||||
// Item1 是移动物体的组件名(如"车轮1"、"车轮2"),需要统一为移动物体本身
|
||||
// Item2 是被撞物体,需要向上查找有意义的父级容器
|
||||
var clashResults = new List<CollisionResult>();
|
||||
foreach (var child in collisionGroup.Children)
|
||||
{
|
||||
if (child is ClashResult clashResult)
|
||||
{
|
||||
var compositeItem1 = animatedObject;
|
||||
var compositeItem2 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item2);
|
||||
|
||||
var collisionResult = new CollisionResult
|
||||
{
|
||||
ClashGuid = clashResult.Guid,
|
||||
DisplayName = clashResult.DisplayName,
|
||||
Status = clashResult.Status,
|
||||
Item1 = compositeItem1,
|
||||
Item2 = compositeItem2,
|
||||
Center = clashResult.Center,
|
||||
Distance = clashResult.Distance,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
clashResults.Add(collisionResult);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 新增:按碰撞对象对去重
|
||||
// 原因:同一个移动物体与同一个被撞物体的多个组件碰撞,需要合并为一个记录
|
||||
var finalClashResults = clashResults
|
||||
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对");
|
||||
|
||||
// 缓存最终结果
|
||||
lock (_clashResultsCacheLock)
|
||||
{
|
||||
_clashDetectiveResultsCache[_currentTestName] = finalClashResults;
|
||||
}
|
||||
LogManager.Info($"[ClashDetective] 已缓存结果:{finalClashResults.Count}个碰撞,测试名称:{_currentTestName}");
|
||||
|
||||
// 更新碰撞计数器
|
||||
_clashDetectiveCollisionCount = clashResults.Count;
|
||||
|
||||
// 保存到数据库(使用去重后的结果)
|
||||
SaveClashDetectiveResultToDatabase(pathName, routeId, finalClashResults, frameRate, duration, detectionGap, animatedObject, isVirtualVehicle,
|
||||
virtualVehicleLength, virtualVehicleWidth, virtualVehicleHeight);
|
||||
|
||||
LogManager.Info($"[ClashDetective] 结果已保存到数据库");
|
||||
|
||||
// 第四步:将分组添加到主测试(由调用方完成)
|
||||
return (collisionGroup, addedMainTest, confirmedCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画结束后统一创建和运行ClashDetective碰撞测试
|
||||
/// </summary>
|
||||
@ -547,316 +817,45 @@ namespace NavisworksTransport
|
||||
|
||||
var doc = Application.ActiveDocument;
|
||||
|
||||
// 使用分组方案:创建一个主测试和一个包含所有碰撞结果的组
|
||||
LogManager.Info("=== 开始分组方案:创建主测试和碰撞分组 ===");
|
||||
|
||||
// 过滤有效的碰撞
|
||||
var validCollisions = collisionResults.Where(collision =>
|
||||
collision.HasPositionInfo &&
|
||||
IsModelItemValid(collision.Item1) &&
|
||||
IsModelItemValid(collision.Item2)
|
||||
).ToList();
|
||||
|
||||
if (validCollisions.Count == 0)
|
||||
{
|
||||
LogManager.Warning("没有有效的碰撞可以创建测试");
|
||||
return;
|
||||
}
|
||||
|
||||
LogManager.Info($"[分组测试] 有效碰撞数量: {validCollisions.Count}");
|
||||
|
||||
// 创建主测试名称
|
||||
var mainTestName = $"碰撞检测_{DateTime.Now:MMdd_HHmmss}";
|
||||
_currentTestName = mainTestName;
|
||||
LogManager.Info($"[分组测试] 创建主测试: {mainTestName}");
|
||||
|
||||
// 移除事务以优化性能(事务开销约30-65%)
|
||||
// 对于碰撞检测场景,事务的必要性很低,即使失败影响也很小
|
||||
// 调用公共方法运行ClashDetective测试并保存到数据库
|
||||
Progress progress = Application.BeginProgress("碰撞检测数据分析中,请稍候...");
|
||||
ClashTest addedMainTest = null;
|
||||
Progress progress = null;
|
||||
|
||||
ClashResultGroup collisionGroup = null;
|
||||
int confirmedCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// 开始进度条(ClashDetective本身会显示进度,这里只提供提示信息)
|
||||
progress = Application.BeginProgress(
|
||||
"碰撞检测数据分析中,请稍候..."
|
||||
var result = RunClashDetectiveTestsAndSaveToDatabase(
|
||||
precomputedCollisions,
|
||||
detectionGap,
|
||||
pathName,
|
||||
routeId,
|
||||
animatedObject,
|
||||
isVirtualVehicle,
|
||||
frameRate,
|
||||
duration,
|
||||
virtualVehicleLength,
|
||||
virtualVehicleWidth,
|
||||
virtualVehicleHeight,
|
||||
progress
|
||||
);
|
||||
|
||||
// 第一步:创建主测试(不包含选择集,纯容器)
|
||||
var mainTest = new ClashTest
|
||||
{
|
||||
DisplayName = mainTestName,
|
||||
// HardConservative 模式的缺点是性能低。如果要提高性能,可以用Hard模式
|
||||
TestType = ClashTestType.Hard,
|
||||
Tolerance = detectionGap,
|
||||
Guid = Guid.Empty,
|
||||
MergeComposites = true
|
||||
};
|
||||
|
||||
// 添加主测试到文档
|
||||
_documentClash.TestsData.TestsAddCopy(mainTest);
|
||||
LogManager.Info($"[分组测试] 主测试已添加到文档");
|
||||
|
||||
// 获取添加后的测试对象引用
|
||||
addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest;
|
||||
if (addedMainTest == null)
|
||||
{
|
||||
LogManager.Error("无法获取添加后的主测试对象");
|
||||
return;
|
||||
}
|
||||
|
||||
// 第二步:创建分组并添加碰撞结果(应用智能去重)
|
||||
// 1. 分组:按碰撞对象对分组
|
||||
var groupedCollisions = validCollisions
|
||||
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
|
||||
.ToList();
|
||||
|
||||
LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对");
|
||||
|
||||
// 缓存去重后的碰撞结果(每组取第一个)
|
||||
lock (_resultsLock)
|
||||
{
|
||||
_deduplicatedCollisionResults.Clear();
|
||||
foreach (var group in groupedCollisions)
|
||||
{
|
||||
_deduplicatedCollisionResults.Add(group.First());
|
||||
}
|
||||
LogManager.Debug($"[去重缓存] 已缓存 {_deduplicatedCollisionResults.Count} 个去重后的碰撞结果");
|
||||
}
|
||||
|
||||
var collisionGroup = new ClashResultGroup
|
||||
{
|
||||
DisplayName = $"碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)"
|
||||
};
|
||||
|
||||
LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}");
|
||||
|
||||
int confirmedCount = 0;
|
||||
int skippedCount = 0;
|
||||
|
||||
// 2. 遍历每一组
|
||||
for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++)
|
||||
{
|
||||
// 检查用户是否取消
|
||||
if (progress.IsCanceled)
|
||||
{
|
||||
LogManager.Info($"[分组测试] 用户取消操作,已处理 {groupIndex}/{groupedCollisions.Count} 组");
|
||||
break;
|
||||
}
|
||||
|
||||
var group = groupedCollisions[groupIndex];
|
||||
|
||||
// 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞
|
||||
// 注意:Distance通常越小(或负值越大)表示碰撞越深,具体取决于计算方式
|
||||
// 这里假设Distance越小越严重
|
||||
var sortedCandidates = group.OrderBy(c => c.Distance).ToList();
|
||||
|
||||
bool pairConfirmed = false;
|
||||
|
||||
// 4. 验证即止:逐个检测候选帧
|
||||
for (int i = 0; i < sortedCandidates.Count; i++)
|
||||
{
|
||||
var candidate = sortedCandidates[i];
|
||||
|
||||
try
|
||||
{
|
||||
// 临时移动动画对象到碰撞位置以执行测试
|
||||
var testAnimatedObject = candidate.Item1;
|
||||
var modelItems = new ModelItemCollection { testAnimatedObject };
|
||||
var targetPosition = candidate.Item1Position;
|
||||
|
||||
// 计算移动偏移
|
||||
var currentBounds = testAnimatedObject.BoundingBox();
|
||||
var currentPos = new Point3D(
|
||||
(currentBounds.Min.X + currentBounds.Max.X) / 2,
|
||||
(currentBounds.Min.Y + currentBounds.Max.Y) / 2,
|
||||
(currentBounds.Min.Z + currentBounds.Max.Z) / 2
|
||||
);
|
||||
|
||||
var offset = new Vector3D(
|
||||
targetPosition.X - currentPos.X,
|
||||
targetPosition.Y - currentPos.Y,
|
||||
targetPosition.Z - currentPos.Z
|
||||
);
|
||||
|
||||
var transform = Transform3D.CreateTranslation(offset);
|
||||
doc.Models.OverridePermanentTransform(modelItems, transform, false);
|
||||
|
||||
// 创建临时测试以获取真实碰撞结果
|
||||
var tempTestName = $"临时验证_{confirmedCount + 1}_{i}_{DateTime.Now:HHmmss_fff}";
|
||||
var tempTest = new ClashTest
|
||||
{
|
||||
DisplayName = tempTestName,
|
||||
TestType = ClashTestType.HardConservative,
|
||||
Tolerance = detectionGap,
|
||||
Guid = Guid.Empty,
|
||||
MergeComposites = true
|
||||
};
|
||||
|
||||
// 设置选择集
|
||||
var selectionA = new ModelItemCollection { candidate.Item1 };
|
||||
var selectionB = new ModelItemCollection { candidate.Item2 };
|
||||
|
||||
tempTest.SelectionA.Selection.CopyFrom(selectionA);
|
||||
tempTest.SelectionB.Selection.CopyFrom(selectionB);
|
||||
|
||||
// 添加并运行临时测试
|
||||
_documentClash.TestsData.TestsAddCopy(tempTest);
|
||||
var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
|
||||
|
||||
if (addedTempTest != null)
|
||||
{
|
||||
// 设置几何类型(非关键操作,失败不影响继续执行)
|
||||
var copyTest = addedTempTest.CreateCopy() as ClashTest;
|
||||
copyTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
|
||||
copyTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
|
||||
_documentClash.TestsData.TestsEditTestFromCopy(addedTempTest, copyTest);
|
||||
|
||||
// 运行测试
|
||||
_documentClash.TestsData.TestsRunTest(addedTempTest);
|
||||
|
||||
// 获取刷新后的测试结果
|
||||
var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
|
||||
if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0)
|
||||
{
|
||||
// !!!发现真实碰撞!!!
|
||||
pairConfirmed = true;
|
||||
confirmedCount++;
|
||||
skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量
|
||||
|
||||
// 将碰撞结果复制到分组中
|
||||
int subResultIndex = 1;
|
||||
foreach (var child in refreshedTempTest.Children)
|
||||
{
|
||||
if (child is ClashResult result)
|
||||
{
|
||||
var copiedResult = result.CreateCopy() as ClashResult;
|
||||
copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID
|
||||
|
||||
// 设置唯一且有意义的碰撞名称
|
||||
// 先找到有意义的容器对象,再获取其名称
|
||||
var container1 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item1);
|
||||
var container2 = ModelItemAnalysisHelper.FindNamedParentContainer(result.Item2);
|
||||
var object1Name = ModelItemAnalysisHelper.GetSafeDisplayName(container1);
|
||||
var object2Name = ModelItemAnalysisHelper.GetSafeDisplayName(container2);
|
||||
|
||||
var timeStamp = DateTime.Now.ToString("HHmmss");
|
||||
copiedResult.DisplayName = $"物流碰撞#{confirmedCount:00}-{subResultIndex:00}_{timeStamp}: {object1Name} ↔ {object2Name}";
|
||||
|
||||
collisionGroup.Children.Add(copiedResult);
|
||||
subResultIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理临时测试
|
||||
_documentClash.TestsData.TestsRemove(refreshedTempTest ?? addedTempTest);
|
||||
|
||||
// 如果已确认碰撞,跳出当前候选循环,不再检测该组剩余帧
|
||||
if (pairConfirmed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception itemEx)
|
||||
{
|
||||
LogManager.Error($"候选检测失败: {itemEx.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[分组测试] ClashDetective检测完成: 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点");
|
||||
|
||||
// 第三步:处理碰撞结果
|
||||
// 🔥 重要:ClashDetective 返回的是几何体级别的碰撞结果
|
||||
// Item1 是移动物体的组件名(如"车轮1"、"车轮2"),需要统一为移动物体本身
|
||||
// Item2 是被撞物体,需要向上查找有意义的父级容器
|
||||
var clashResults = new List<CollisionResult>();
|
||||
foreach (var child in collisionGroup.Children)
|
||||
{
|
||||
if (child is ClashResult clashResult)
|
||||
{
|
||||
// 🔥 修改:Item1 直接使用移动物体本身(animatedObject)
|
||||
// 原因:ClashDetective 返回的是组件名,需要统一为移动物体本身,这样才有重复的记录可以合并
|
||||
var compositeItem1 = animatedObject; // 直接使用移动物体本身
|
||||
|
||||
// Item2 保持现有逻辑:向上查找有意义的父级容器
|
||||
var compositeItem2 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item2);
|
||||
|
||||
var collisionResult = new CollisionResult
|
||||
{
|
||||
ClashGuid = clashResult.Guid,
|
||||
DisplayName = clashResult.DisplayName,
|
||||
Status = clashResult.Status,
|
||||
Item1 = compositeItem1, // 移动物体本身
|
||||
Item2 = compositeItem2, // 被撞物体的父级容器
|
||||
Center = clashResult.Center,
|
||||
Distance = clashResult.Distance,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
clashResults.Add(collisionResult);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 新增:按碰撞对象对去重
|
||||
// 原因:同一个移动物体与同一个被撞物体的多个组件碰撞,需要合并为一个记录
|
||||
var finalClashResults = clashResults
|
||||
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对");
|
||||
|
||||
// 缓存最终结果
|
||||
lock (_clashResultsCacheLock)
|
||||
{
|
||||
_clashDetectiveResultsCache[_currentTestName] = finalClashResults;
|
||||
}
|
||||
LogManager.Info($"已缓存ClashDetective结果:{finalClashResults.Count}个碰撞,测试名称:{_currentTestName}");
|
||||
|
||||
// 更新碰撞计数器
|
||||
_clashDetectiveCollisionCount = clashResults.Count;
|
||||
|
||||
// 保存到数据库(使用去重后的结果)
|
||||
SaveClashDetectiveResultToDatabase(pathName, routeId, finalClashResults, frameRate, duration, detectionGap, animatedObject, isVirtualVehicle,
|
||||
virtualVehicleLength, virtualVehicleWidth, virtualVehicleHeight);
|
||||
|
||||
// 第四步:将分组添加到主测试
|
||||
if (collisionGroup.Children.Count > 0)
|
||||
{
|
||||
_documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Warning("[分组测试] 分组为空(未检测到真实几何碰撞),未添加到主测试");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[分组测试] 创建分组测试失败: {ex.Message}");
|
||||
|
||||
// 异常时清理已创建的主测试
|
||||
if (addedMainTest != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_documentClash.TestsData.TestsRemove(addedMainTest);
|
||||
LogManager.Info("[分组测试] 已清理主测试");
|
||||
}
|
||||
catch (Exception cleanupEx)
|
||||
{
|
||||
LogManager.Warning($"[分组测试] 清理主测试失败: {cleanupEx.Message}");
|
||||
}
|
||||
}
|
||||
collisionGroup = result.collisionGroup;
|
||||
addedMainTest = result.addedMainTest;
|
||||
confirmedCount = result.confirmedCount;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 确保关闭进度条
|
||||
if (progress != null)
|
||||
{
|
||||
Application.EndProgress();
|
||||
}
|
||||
Application.EndProgress();
|
||||
}
|
||||
|
||||
// UI操作:将分组添加到主测试
|
||||
if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0)
|
||||
{
|
||||
_documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Warning("[分组测试] 分组为空(未检测到真实几何碰撞),未添加到主测试");
|
||||
}
|
||||
|
||||
// 碰撞测试完成后,将物体恢复到动画终点位置
|
||||
@ -910,7 +909,7 @@ namespace NavisworksTransport
|
||||
// 🔥 无论是否有主测试,都触发碰撞检测完成事件,通知生成报告
|
||||
// 使用ClashDetective权威结果,而不是预计算结果
|
||||
// 如果权威结果为0个碰撞,传入空列表以触发祝贺对话框
|
||||
var finalCollisions = _clashDetectiveCollisionCount > 0 ? validCollisions : new List<CollisionResult>();
|
||||
var finalCollisions = _clashDetectiveCollisionCount > 0 ? collisionResults : new List<CollisionResult>();
|
||||
LogManager.Info($"触发CollisionDetected事件,通知生成报告({_clashDetectiveCollisionCount}个碰撞,权威数据)");
|
||||
var eventArgs = new CollisionDetectedEventArgs(finalCollisions);
|
||||
OnCollisionDetected(eventArgs);
|
||||
@ -1279,6 +1278,29 @@ namespace NavisworksTransport
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化碰撞检测缓存(用于预计算前的准备工作)
|
||||
/// </summary>
|
||||
/// <param name="animatedObject">移动物体</param>
|
||||
public static void InitializeCollisionDetectionCache(ModelItem animatedObject)
|
||||
{
|
||||
LogManager.Info("[碰撞检测] 开始初始化碰撞检测缓存...");
|
||||
|
||||
// 1. 清除所有缓存
|
||||
ClearAllCaches();
|
||||
|
||||
// 2. 构建几何对象缓存(包含移动物体)
|
||||
BuildNonHidddenGeometryItemsCache();
|
||||
|
||||
// 3. 构建通道对象缓存
|
||||
Instance.BuildChannelObjectsCache();
|
||||
|
||||
// 4. 设置移动物体并重新构建几何对象缓存(排除移动物体)
|
||||
SetAnimatedObject(animatedObject);
|
||||
|
||||
LogManager.Info("[碰撞检测] 碰撞检测缓存初始化完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取通道对象缓存(供外部使用)
|
||||
/// </summary>
|
||||
@ -1549,7 +1571,7 @@ namespace NavisworksTransport
|
||||
|
||||
// 过滤有效的碰撞
|
||||
var validCollisions = precomputedCollisions
|
||||
.Where(result => IsModelItemValid(result.Item1) && IsModelItemValid(result.Item2))
|
||||
.Where(collision => IsModelItemValid(collision.Item1) && IsModelItemValid(collision.Item2))
|
||||
.ToList();
|
||||
|
||||
if (validCollisions.Count == 0)
|
||||
@ -1562,124 +1584,38 @@ namespace NavisworksTransport
|
||||
_animationCollisionCount = validCollisions.Count;
|
||||
LogManager.Info($"[批处理] 原始记录: {precomputedCollisions.Count},有效碰撞: {_animationCollisionCount}");
|
||||
|
||||
// 创建主测试名称
|
||||
var mainTestName = $"碰撞检测_{DateTime.Now:MMdd_HHmmss}";
|
||||
_currentTestName = mainTestName;
|
||||
LogManager.Info($"[批处理] 创建主测试: {mainTestName}");
|
||||
// 调用公共方法运行ClashDetective测试并保存到数据库
|
||||
var result = RunClashDetectiveTestsAndSaveToDatabase(
|
||||
precomputedCollisions,
|
||||
detectionGap,
|
||||
pathName,
|
||||
routeId,
|
||||
animatedObject,
|
||||
isVirtualVehicle,
|
||||
frameRate,
|
||||
duration,
|
||||
virtualVehicleLength,
|
||||
virtualVehicleWidth,
|
||||
virtualVehicleHeight,
|
||||
null
|
||||
);
|
||||
|
||||
// 创建主测试
|
||||
var mainTest = new ClashTest
|
||||
var collisionGroup = result.collisionGroup;
|
||||
var addedMainTest = result.addedMainTest;
|
||||
var confirmedCount = result.confirmedCount;
|
||||
|
||||
// 添加分组到主测试(使用 TestsAddCopy 方法)
|
||||
if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0)
|
||||
{
|
||||
DisplayName = mainTestName,
|
||||
TestType = ClashTestType.Hard,
|
||||
Tolerance = detectionGap,
|
||||
Guid = Guid.Empty,
|
||||
MergeComposites = true
|
||||
};
|
||||
|
||||
// 添加主测试到文档
|
||||
_documentClash.TestsData.TestsAddCopy(mainTest);
|
||||
LogManager.Info($"[批处理] 主测试已添加到文档");
|
||||
|
||||
// 获取添加后的测试对象引用
|
||||
ClashTest addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest;
|
||||
if (addedMainTest == null)
|
||||
_documentClash.TestsData.TestsAddCopy(addedMainTest, collisionGroup);
|
||||
LogManager.Info($"[批处理] 碰撞分组已添加到主测试,共 {confirmedCount} 个碰撞");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Error("[批处理] 无法获取添加后的主测试对象");
|
||||
return null;
|
||||
LogManager.Warning("[批处理] 分组为空(未检测到真实几何碰撞),未添加到主测试");
|
||||
}
|
||||
|
||||
// 智能去重:按碰撞对象对分组
|
||||
var groupedCollisions = validCollisions
|
||||
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
|
||||
.ToList();
|
||||
|
||||
LogManager.Info($"[批处理] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对");
|
||||
|
||||
// 缓存去重后的碰撞结果
|
||||
lock (_resultsLock)
|
||||
{
|
||||
_deduplicatedCollisionResults.Clear();
|
||||
foreach (var group in groupedCollisions)
|
||||
{
|
||||
_deduplicatedCollisionResults.Add(group.First());
|
||||
}
|
||||
LogManager.Debug($"[批处理] 已缓存 {_deduplicatedCollisionResults.Count} 个去重后的碰撞结果");
|
||||
}
|
||||
|
||||
// 创建碰撞分组
|
||||
var collisionGroup = new ClashResultGroup
|
||||
{
|
||||
DisplayName = $"碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)"
|
||||
};
|
||||
|
||||
LogManager.Info($"[批处理] 创建碰撞分组: {collisionGroup.DisplayName}");
|
||||
|
||||
// 为每个唯一碰撞对创建碰撞结果
|
||||
int confirmedCount = 0;
|
||||
for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++)
|
||||
{
|
||||
var group = groupedCollisions[groupIndex];
|
||||
var collision = group.First();
|
||||
|
||||
// 创建碰撞结果(不执行实际测试,直接创建结果)
|
||||
var clashResult = new ClashResult();
|
||||
|
||||
// 使用反射设置只读属性(因为ClashResult的Item1和Item2是只读的)
|
||||
var item1Property = typeof(ClashResult).GetProperty("Item1");
|
||||
var item2Property = typeof(ClashResult).GetProperty("Item2");
|
||||
|
||||
if (item1Property != null)
|
||||
{
|
||||
item1Property.SetValue(clashResult, collision.Item1);
|
||||
}
|
||||
if (item2Property != null)
|
||||
{
|
||||
item2Property.SetValue(clashResult, collision.Item2);
|
||||
}
|
||||
|
||||
clashResult.DisplayName = $"碰撞_{groupIndex + 1}";
|
||||
clashResult.Status = ClashResultStatus.New;
|
||||
clashResult.Center = collision.Center;
|
||||
clashResult.Distance = collision.Distance;
|
||||
|
||||
collisionGroup.Children.Add(clashResult);
|
||||
confirmedCount++;
|
||||
}
|
||||
|
||||
// 添加分组到主测试
|
||||
addedMainTest.Children.Add(collisionGroup);
|
||||
LogManager.Info($"[批处理] 碰撞分组已添加到主测试,共 {confirmedCount} 个碰撞");
|
||||
|
||||
// 设置权威碰撞计数
|
||||
_clashDetectiveCollisionCount = confirmedCount;
|
||||
LogManager.Info($"[批处理] 权威碰撞计数: {_clashDetectiveCollisionCount}");
|
||||
|
||||
// 保存结果到数据库
|
||||
try
|
||||
{
|
||||
// 保存到数据库(使用去重后的结果)
|
||||
SaveClashDetectiveResultToDatabase(
|
||||
pathName,
|
||||
routeId,
|
||||
_deduplicatedCollisionResults,
|
||||
frameRate,
|
||||
duration,
|
||||
detectionGap,
|
||||
animatedObject,
|
||||
isVirtualVehicle,
|
||||
virtualVehicleLength,
|
||||
virtualVehicleWidth,
|
||||
virtualVehicleHeight
|
||||
);
|
||||
LogManager.Info($"[批处理] 结果已保存到数据库");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[批处理] 保存结果到数据库失败: {ex.Message}");
|
||||
}
|
||||
|
||||
return mainTestName;
|
||||
return addedMainTest?.DisplayName;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -4,8 +4,9 @@ using System.Collections.Generic;
|
||||
namespace NavisworksTransport.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 批处理碰撞检测任务
|
||||
/// 批处理任务(已过时,请使用 BatchQueueItem)
|
||||
/// </summary>
|
||||
[Obsolete("请使用 BatchQueueItem 代替")]
|
||||
public class BatchCollisionTask
|
||||
{
|
||||
public int Id { get; set; }
|
||||
@ -20,29 +21,45 @@ namespace NavisworksTransport.Core.Models
|
||||
public int FailedItems { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public List<BatchTaskItem> Items { get; set; } = new List<BatchTaskItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 进度显示文本(用于UI绑定)
|
||||
/// </summary>
|
||||
public string ProgressDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TotalItems == 0) return "0/0";
|
||||
return $"{CompletedItems}/{TotalItems}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批处理任务状态
|
||||
/// 批处理任务状态(已过时,请使用 BatchQueueStatus)
|
||||
/// </summary>
|
||||
[Obsolete("请使用 BatchQueueStatus 代替")]
|
||||
public enum BatchTaskStatus
|
||||
{
|
||||
Pending, // 等待执行
|
||||
Running, // 正在执行
|
||||
Completed, // 已完成
|
||||
Failed, // 执行失败
|
||||
Cancelled // 已取消
|
||||
Pending, Running, Completed, Failed, Cancelled
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批处理任务项(已过时)
|
||||
/// </summary>
|
||||
[Obsolete("请使用 BatchQueueItem 代替")]
|
||||
public class BatchTaskItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int BatchTaskId { get; set; }
|
||||
public string PathRouteId { get; set; }
|
||||
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 VehicleObjectId { get; set; }
|
||||
public string VehicleObjectName { get; set; }
|
||||
public List<string> DetectionItems { get; set; } = new List<string>();
|
||||
public string ClashDetectiveTestName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批处理任务项状态(已过时)
|
||||
/// </summary>
|
||||
[Obsolete("请使用 BatchQueueStatus 代替")]
|
||||
public enum BatchTaskItemStatus
|
||||
{
|
||||
Pending, Running, Completed, Failed, Skipped
|
||||
}
|
||||
}
|
||||
55
src/Core/Models/BatchQueueItem.cs
Normal file
55
src/Core/Models/BatchQueueItem.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NavisworksTransport.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 批处理队列项 - 简化版,只包含一条路径的碰撞检测任务
|
||||
/// </summary>
|
||||
public class BatchQueueItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string PathRouteId { get; set; }
|
||||
public string PathRouteName { get; set; }
|
||||
public BatchQueueStatus Status { get; set; }
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public DateTime? StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
// 动画配置
|
||||
public int FrameRate { get; set; }
|
||||
public double DurationSeconds { get; set; }
|
||||
|
||||
// 碰撞检测配置
|
||||
public double DetectionGapMeters { get; set; }
|
||||
|
||||
// 运动物体配置
|
||||
public bool IsVirtualVehicle { get; set; }
|
||||
public double VirtualVehicleLength { get; set; }
|
||||
public double VirtualVehicleWidth { get; set; }
|
||||
public double VirtualVehicleHeight { get; set; }
|
||||
public string VehicleObjectId { get; set; }
|
||||
public string VehicleObjectName { get; set; }
|
||||
|
||||
// 被检测项
|
||||
public List<string> DetectionItems { get; set; } = new List<string>();
|
||||
public bool DetectAllObjects { get; set; } = true;
|
||||
|
||||
// 结果
|
||||
public string ClashDetectiveTestName { get; set; }
|
||||
public int? CollisionCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批处理队列项状态
|
||||
/// </summary>
|
||||
public enum BatchQueueStatus
|
||||
{
|
||||
Pending, // 等待执行
|
||||
Running, // 正在执行
|
||||
Completed, // 已完成
|
||||
Failed, // 执行失败
|
||||
Skipped // 已跳过
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NavisworksTransport.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 批处理任务项
|
||||
/// </summary>
|
||||
public class BatchTaskItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int BatchTaskId { get; set; }
|
||||
public string PathRouteId { get; set; }
|
||||
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 VehicleObjectId { get; set; }
|
||||
public string VehicleObjectName { get; set; }
|
||||
public List<string> DetectionItems { get; set; } = new List<string>();
|
||||
public string ClashDetectiveTestName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批处理任务项状态
|
||||
/// </summary>
|
||||
public enum BatchTaskItemStatus
|
||||
{
|
||||
Pending, // 等待执行
|
||||
Running, // 正在执行
|
||||
Completed, // 已完成
|
||||
Failed, // 执行失败
|
||||
Skipped // 已跳过
|
||||
}
|
||||
}
|
||||
@ -28,19 +28,44 @@ namespace NavisworksTransport.Core.Models
|
||||
|
||||
// UI专用属性
|
||||
public bool IsVirtualVehicle { get; set; }
|
||||
public string VehicleSizeText { get; set; }
|
||||
public double VirtualVehicleLength { get; set; } = 1.0;
|
||||
public double VirtualVehicleWidth { get; set; } = 1.0;
|
||||
public double VirtualVehicleHeight { get; set; } = 2.0;
|
||||
|
||||
// 显示属性
|
||||
public string VehicleTypeDisplay => IsVirtualVehicle ? "虚拟物体" : "真实物体";
|
||||
public string VehicleInfoText => IsVirtualVehicle
|
||||
? $"{VirtualVehicleLength:F2}×{VirtualVehicleWidth:F2}×{VirtualVehicleHeight:F2} 米"
|
||||
: (VehicleObjectName ?? "未选择");
|
||||
public string DetectionItemsText => DetectionItems?.Count > 0 ? $"{DetectionItems.Count} 项" : "全部";
|
||||
public string ConfigStatusText => ConfigOverride != null ? "自定义" : "默认";
|
||||
public string VehicleSizeText => IsVirtualVehicle
|
||||
? $"{VirtualVehicleLength:F2}×{VirtualVehicleWidth:F2}×{VirtualVehicleHeight:F2}"
|
||||
: "-";
|
||||
|
||||
/// <summary>
|
||||
/// 转换为核心配置对象
|
||||
/// </summary>
|
||||
public BatchTaskItemConfig ToCoreConfig()
|
||||
{
|
||||
var configOverride = ConfigOverride;
|
||||
if (IsVirtualVehicle && configOverride == null)
|
||||
{
|
||||
// 如果使用虚拟物体但没有配置覆盖,创建默认配置
|
||||
configOverride = new CollisionDetectionConfig
|
||||
{
|
||||
VirtualVehicleLength = VirtualVehicleLength,
|
||||
VirtualVehicleWidth = VirtualVehicleWidth,
|
||||
VirtualVehicleHeight = VirtualVehicleHeight
|
||||
};
|
||||
}
|
||||
|
||||
return new BatchTaskItemConfig
|
||||
{
|
||||
PathRouteId = PathRouteId,
|
||||
PathRouteName = PathRouteName,
|
||||
SequenceNumber = SequenceNumber,
|
||||
ConfigOverride = ConfigOverride,
|
||||
ConfigOverride = configOverride,
|
||||
VehicleObjectId = VehicleObjectId,
|
||||
VehicleObjectName = VehicleObjectName,
|
||||
DetectionItems = DetectionItems
|
||||
|
||||
@ -212,50 +212,37 @@ namespace NavisworksTransport
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_objects_result ON ClashDetectiveCollisionObjects(ResultId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_objects_path ON ClashDetectiveCollisionObjects(PathId)");
|
||||
|
||||
// 8. 批处理任务表
|
||||
// 8. 批处理队列表(简化版,只维护FIFO队列)
|
||||
ExecuteNonQuery(@"
|
||||
CREATE TABLE IF NOT EXISTS BatchTasks (
|
||||
CREATE TABLE IF NOT EXISTS BatchQueueItems (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Name TEXT NOT NULL,
|
||||
Description TEXT,
|
||||
CreatedTime TEXT NOT NULL,
|
||||
Status TEXT NOT NULL,
|
||||
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
|
||||
)
|
||||
");
|
||||
|
||||
// 9. 批处理任务项表
|
||||
ExecuteNonQuery(@"
|
||||
CREATE TABLE IF NOT EXISTS BatchTaskItems (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
BatchTaskId INTEGER NOT NULL,
|
||||
PathRouteId TEXT NOT NULL,
|
||||
PathRouteName TEXT NOT NULL,
|
||||
SequenceNumber INTEGER NOT NULL,
|
||||
Status TEXT NOT NULL,
|
||||
CreatedTime TEXT NOT NULL,
|
||||
StartTime TEXT,
|
||||
EndTime TEXT,
|
||||
ErrorMessage TEXT,
|
||||
ConfigOverride TEXT,
|
||||
FrameRate INTEGER NOT NULL,
|
||||
DurationSeconds REAL NOT NULL,
|
||||
DetectionGapMeters REAL NOT NULL,
|
||||
IsVirtualVehicle INTEGER NOT NULL,
|
||||
VirtualVehicleLength REAL,
|
||||
VirtualVehicleWidth REAL,
|
||||
VirtualVehicleHeight REAL,
|
||||
VehicleObjectId TEXT,
|
||||
VehicleObjectName TEXT,
|
||||
DetectionItems TEXT,
|
||||
DetectAllObjects INTEGER NOT NULL DEFAULT 1,
|
||||
ClashDetectiveTestName TEXT,
|
||||
FOREIGN KEY (BatchTaskId) REFERENCES BatchTasks(Id) ON DELETE CASCADE
|
||||
CollisionCount INTEGER
|
||||
)
|
||||
");
|
||||
|
||||
// 批处理表索引
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_tasks_status ON BatchTasks(Status)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_tasks_created ON BatchTasks(CreatedTime)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_task_items_batch ON BatchTaskItems(BatchTaskId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_task_items_path ON BatchTaskItems(PathRouteId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_task_items_status ON BatchTaskItems(Status)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_status ON BatchQueueItems(Status)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_created ON BatchQueueItems(CreatedTime)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_path ON BatchQueueItems(PathRouteId)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1566,11 +1553,11 @@ namespace NavisworksTransport
|
||||
using (var cmd = new SQLiteCommand(_connection))
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
SELECT Id, RouteId, Name, CreatedTime, LastModified, PathData, TotalDistance, TotalDuration, CurveType, CurveTension, IsCurved
|
||||
SELECT Id, Name, CreatedTime, LastModified, TotalLength
|
||||
FROM PathRoutes
|
||||
WHERE RouteId = @RouteId";
|
||||
WHERE Id = @Id";
|
||||
|
||||
cmd.Parameters.AddWithValue("@RouteId", routeId);
|
||||
cmd.Parameters.AddWithValue("@Id", routeId);
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
@ -1578,14 +1565,15 @@ namespace NavisworksTransport
|
||||
{
|
||||
var route = new PathRoute(reader["Name"].ToString())
|
||||
{
|
||||
Id = reader["RouteId"].ToString(),
|
||||
Id = reader["Id"].ToString(),
|
||||
CreatedTime = DateTime.Parse(reader["CreatedTime"].ToString()),
|
||||
LastModified = DateTime.Parse(reader["LastModified"].ToString()),
|
||||
TotalLength = Convert.ToDouble(reader["TotalDistance"])
|
||||
TotalLength = Convert.ToDouble(reader["TotalLength"])
|
||||
};
|
||||
|
||||
// 加载路径点
|
||||
// 加载路径点和路径边
|
||||
LoadPathPoints(route);
|
||||
LoadPathEdges(route);
|
||||
return route;
|
||||
}
|
||||
return null;
|
||||
@ -1596,6 +1584,242 @@ namespace NavisworksTransport
|
||||
|
||||
#endregion
|
||||
|
||||
#region 批处理队列管理(简化版)
|
||||
|
||||
/// <summary>
|
||||
/// 创建批处理队列项
|
||||
/// </summary>
|
||||
public async Task<int> CreateBatchQueueItemAsync(BatchQueueItem item)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
using (var cmd = new SQLiteCommand(_connection))
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
INSERT INTO BatchQueueItems (
|
||||
PathRouteId, PathRouteName, Status, CreatedTime, StartTime, EndTime, ErrorMessage,
|
||||
FrameRate, DurationSeconds, DetectionGapMeters,
|
||||
IsVirtualVehicle, VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight,
|
||||
VehicleObjectId, VehicleObjectName, DetectionItems, DetectAllObjects,
|
||||
ClashDetectiveTestName, CollisionCount
|
||||
)
|
||||
VALUES (
|
||||
@PathRouteId, @PathRouteName, @Status, @CreatedTime, @StartTime, @EndTime, @ErrorMessage,
|
||||
@FrameRate, @DurationSeconds, @DetectionGapMeters,
|
||||
@IsVirtualVehicle, @VirtualVehicleLength, @VirtualVehicleWidth, @VirtualVehicleHeight,
|
||||
@VehicleObjectId, @VehicleObjectName, @DetectionItems, @DetectAllObjects,
|
||||
@ClashDetectiveTestName, @CollisionCount
|
||||
);
|
||||
SELECT last_insert_rowid();";
|
||||
|
||||
cmd.Parameters.AddWithValue("@PathRouteId", item.PathRouteId);
|
||||
cmd.Parameters.AddWithValue("@PathRouteName", item.PathRouteName ?? "");
|
||||
cmd.Parameters.AddWithValue("@Status", item.Status.ToString());
|
||||
cmd.Parameters.AddWithValue("@CreatedTime", item.CreatedTime.ToString("o"));
|
||||
cmd.Parameters.AddWithValue("@StartTime", item.StartTime?.ToString("o") ?? "");
|
||||
cmd.Parameters.AddWithValue("@EndTime", item.EndTime?.ToString("o") ?? "");
|
||||
cmd.Parameters.AddWithValue("@ErrorMessage", item.ErrorMessage ?? "");
|
||||
cmd.Parameters.AddWithValue("@FrameRate", item.FrameRate);
|
||||
cmd.Parameters.AddWithValue("@DurationSeconds", item.DurationSeconds);
|
||||
cmd.Parameters.AddWithValue("@DetectionGapMeters", item.DetectionGapMeters);
|
||||
cmd.Parameters.AddWithValue("@IsVirtualVehicle", item.IsVirtualVehicle ? 1 : 0);
|
||||
cmd.Parameters.AddWithValue("@VirtualVehicleLength", item.VirtualVehicleLength);
|
||||
cmd.Parameters.AddWithValue("@VirtualVehicleWidth", item.VirtualVehicleWidth);
|
||||
cmd.Parameters.AddWithValue("@VirtualVehicleHeight", item.VirtualVehicleHeight);
|
||||
cmd.Parameters.AddWithValue("@VehicleObjectId", item.VehicleObjectId ?? "");
|
||||
cmd.Parameters.AddWithValue("@VehicleObjectName", item.VehicleObjectName ?? "");
|
||||
cmd.Parameters.AddWithValue("@DetectionItems", item.DetectionItems != null ? string.Join(",", item.DetectionItems) : "");
|
||||
cmd.Parameters.AddWithValue("@DetectAllObjects", item.DetectAllObjects ? 1 : 0);
|
||||
cmd.Parameters.AddWithValue("@ClashDetectiveTestName", item.ClashDetectiveTestName ?? "");
|
||||
cmd.Parameters.AddWithValue("@CollisionCount", item.CollisionCount ?? (object)DBNull.Value);
|
||||
|
||||
return Convert.ToInt32(cmd.ExecuteScalar());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取批处理队列项
|
||||
/// </summary>
|
||||
public async Task<BatchQueueItem> GetBatchQueueItemAsync(int itemId)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
using (var cmd = new SQLiteCommand(_connection))
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
SELECT Id, PathRouteId, PathRouteName, Status, CreatedTime, StartTime, EndTime, ErrorMessage,
|
||||
FrameRate, DurationSeconds, DetectionGapMeters,
|
||||
IsVirtualVehicle, VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight,
|
||||
VehicleObjectId, VehicleObjectName, DetectionItems, DetectAllObjects,
|
||||
ClashDetectiveTestName, CollisionCount
|
||||
FROM BatchQueueItems
|
||||
WHERE Id = @Id";
|
||||
|
||||
cmd.Parameters.AddWithValue("@Id", itemId);
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
return new BatchQueueItem
|
||||
{
|
||||
Id = Convert.ToInt32(reader["Id"]),
|
||||
PathRouteId = reader["PathRouteId"].ToString(),
|
||||
PathRouteName = reader["PathRouteName"].ToString(),
|
||||
Status = (BatchQueueStatus)Enum.Parse(typeof(BatchQueueStatus), reader["Status"].ToString()),
|
||||
CreatedTime = DateTime.Parse(reader["CreatedTime"].ToString()),
|
||||
StartTime = reader["StartTime"].ToString() != "" ? (DateTime?)DateTime.Parse(reader["StartTime"].ToString()) : null,
|
||||
EndTime = reader["EndTime"].ToString() != "" ? (DateTime?)DateTime.Parse(reader["EndTime"].ToString()) : null,
|
||||
ErrorMessage = reader["ErrorMessage"].ToString(),
|
||||
FrameRate = Convert.ToInt32(reader["FrameRate"]),
|
||||
DurationSeconds = Convert.ToDouble(reader["DurationSeconds"]),
|
||||
DetectionGapMeters = Convert.ToDouble(reader["DetectionGapMeters"]),
|
||||
IsVirtualVehicle = Convert.ToBoolean(reader["IsVirtualVehicle"]),
|
||||
VirtualVehicleLength = Convert.ToDouble(reader["VirtualVehicleLength"]),
|
||||
VirtualVehicleWidth = Convert.ToDouble(reader["VirtualVehicleWidth"]),
|
||||
VirtualVehicleHeight = Convert.ToDouble(reader["VirtualVehicleHeight"]),
|
||||
VehicleObjectId = reader["VehicleObjectId"].ToString(),
|
||||
VehicleObjectName = reader["VehicleObjectName"].ToString(),
|
||||
DetectionItems = reader["DetectionItems"].ToString() != "" ? reader["DetectionItems"].ToString().Split(',').ToList() : new List<string>(),
|
||||
DetectAllObjects = Convert.ToBoolean(reader["DetectAllObjects"]),
|
||||
ClashDetectiveTestName = reader["ClashDetectiveTestName"].ToString(),
|
||||
CollisionCount = !Convert.IsDBNull(reader["CollisionCount"]) ? (int?)Convert.ToInt32(reader["CollisionCount"]) : null
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取批处理队列项列表
|
||||
/// </summary>
|
||||
public async Task<List<BatchQueueItem>> GetBatchQueueItemsAsync(BatchQueueStatus? statusFilter = null, int limit = 50, int offset = 0)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var items = new List<BatchQueueItem>();
|
||||
using (var cmd = new SQLiteCommand(_connection))
|
||||
{
|
||||
var sql = @"
|
||||
SELECT Id, PathRouteId, PathRouteName, Status, CreatedTime, StartTime, EndTime, ErrorMessage,
|
||||
FrameRate, DurationSeconds, DetectionGapMeters,
|
||||
IsVirtualVehicle, VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight,
|
||||
VehicleObjectId, VehicleObjectName, DetectionItems, DetectAllObjects,
|
||||
ClashDetectiveTestName, CollisionCount
|
||||
FROM BatchQueueItems";
|
||||
var conditions = new List<string>();
|
||||
|
||||
if (statusFilter.HasValue)
|
||||
{
|
||||
conditions.Add("Status = @Status");
|
||||
cmd.Parameters.AddWithValue("@Status", statusFilter.Value.ToString());
|
||||
}
|
||||
|
||||
if (conditions.Count > 0)
|
||||
{
|
||||
sql += " WHERE " + string.Join(" AND ", conditions);
|
||||
}
|
||||
|
||||
sql += " ORDER BY CreatedTime DESC";
|
||||
|
||||
if (limit > 0)
|
||||
{
|
||||
sql += " LIMIT @Limit";
|
||||
cmd.Parameters.AddWithValue("@Limit", limit);
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
sql += " OFFSET @Offset";
|
||||
cmd.Parameters.AddWithValue("@Offset", offset);
|
||||
}
|
||||
|
||||
cmd.CommandText = sql;
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
items.Add(new BatchQueueItem
|
||||
{
|
||||
Id = Convert.ToInt32(reader["Id"]),
|
||||
PathRouteId = reader["PathRouteId"].ToString(),
|
||||
PathRouteName = reader["PathRouteName"].ToString(),
|
||||
Status = (BatchQueueStatus)Enum.Parse(typeof(BatchQueueStatus), reader["Status"].ToString()),
|
||||
CreatedTime = DateTime.Parse(reader["CreatedTime"].ToString()),
|
||||
StartTime = reader["StartTime"].ToString() != "" ? (DateTime?)DateTime.Parse(reader["StartTime"].ToString()) : null,
|
||||
EndTime = reader["EndTime"].ToString() != "" ? (DateTime?)DateTime.Parse(reader["EndTime"].ToString()) : null,
|
||||
ErrorMessage = reader["ErrorMessage"].ToString(),
|
||||
FrameRate = Convert.ToInt32(reader["FrameRate"]),
|
||||
DurationSeconds = Convert.ToDouble(reader["DurationSeconds"]),
|
||||
DetectionGapMeters = Convert.ToDouble(reader["DetectionGapMeters"]),
|
||||
IsVirtualVehicle = Convert.ToBoolean(reader["IsVirtualVehicle"]),
|
||||
VirtualVehicleLength = Convert.ToDouble(reader["VirtualVehicleLength"]),
|
||||
VirtualVehicleWidth = Convert.ToDouble(reader["VirtualVehicleWidth"]),
|
||||
VirtualVehicleHeight = Convert.ToDouble(reader["VirtualVehicleHeight"]),
|
||||
VehicleObjectId = reader["VehicleObjectId"].ToString(),
|
||||
VehicleObjectName = reader["VehicleObjectName"].ToString(),
|
||||
DetectionItems = reader["DetectionItems"].ToString() != "" ? reader["DetectionItems"].ToString().Split(',').ToList() : new List<string>(),
|
||||
DetectAllObjects = Convert.ToBoolean(reader["DetectAllObjects"]),
|
||||
ClashDetectiveTestName = reader["ClashDetectiveTestName"].ToString(),
|
||||
CollisionCount = !Convert.IsDBNull(reader["CollisionCount"]) ? (int?)Convert.ToInt32(reader["CollisionCount"]) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新批处理队列项
|
||||
/// </summary>
|
||||
public async Task UpdateBatchQueueItemAsync(BatchQueueItem item)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using (var cmd = new SQLiteCommand(_connection))
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
UPDATE BatchQueueItems
|
||||
SET Status = @Status, StartTime = @StartTime, EndTime = @EndTime, ErrorMessage = @ErrorMessage,
|
||||
ClashDetectiveTestName = @ClashDetectiveTestName, CollisionCount = @CollisionCount
|
||||
WHERE Id = @Id";
|
||||
|
||||
cmd.Parameters.AddWithValue("@Status", item.Status.ToString());
|
||||
cmd.Parameters.AddWithValue("@StartTime", item.StartTime?.ToString("o") ?? "");
|
||||
cmd.Parameters.AddWithValue("@EndTime", item.EndTime?.ToString("o") ?? "");
|
||||
cmd.Parameters.AddWithValue("@ErrorMessage", item.ErrorMessage ?? "");
|
||||
cmd.Parameters.AddWithValue("@ClashDetectiveTestName", item.ClashDetectiveTestName ?? "");
|
||||
cmd.Parameters.AddWithValue("@CollisionCount", item.CollisionCount ?? (object)DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Id", item.Id);
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除批处理队列项
|
||||
/// </summary>
|
||||
public async Task DeleteBatchQueueItemAsync(int itemId)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using (var cmd = new SQLiteCommand(_connection))
|
||||
{
|
||||
cmd.CommandText = "DELETE FROM BatchQueueItems WHERE Id = @Id";
|
||||
cmd.Parameters.AddWithValue("@Id", itemId);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
|
||||
41
src/UI/WPF/Converters/BatchQueueStatusConverter.cs
Normal file
41
src/UI/WPF/Converters/BatchQueueStatusConverter.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 批处理队列状态转换器,将BatchQueueStatus枚举转换为中文显示
|
||||
/// </summary>
|
||||
public class BatchQueueStatusConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is BatchQueueStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case BatchQueueStatus.Pending:
|
||||
return "待处理";
|
||||
case BatchQueueStatus.Running:
|
||||
return "执行中";
|
||||
case BatchQueueStatus.Completed:
|
||||
return "已完成";
|
||||
case BatchQueueStatus.Failed:
|
||||
return "失败";
|
||||
case BatchQueueStatus.Skipped:
|
||||
return "已跳过";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
return "待处理";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/UI/WPF/Converters/BatchQueueStatusHelper.cs
Normal file
24
src/UI/WPF/Converters/BatchQueueStatusHelper.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 批处理队列状态辅助类,提供所有状态选项
|
||||
/// </summary>
|
||||
public static class BatchQueueStatusHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有队列状态(包括"全部"选项)
|
||||
/// </summary>
|
||||
public static List<BatchQueueStatus?> AllStatuses => new List<BatchQueueStatus?>
|
||||
{
|
||||
null, // 表示"全部"
|
||||
BatchQueueStatus.Pending,
|
||||
BatchQueueStatus.Running,
|
||||
BatchQueueStatus.Completed,
|
||||
BatchQueueStatus.Failed,
|
||||
BatchQueueStatus.Skipped
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@ -9,6 +8,7 @@ using System.Windows.Input;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using Autodesk.Navisworks.Api.Clash;
|
||||
using NavisworksTransport.Core;
|
||||
using NavisworksTransport.Core.Models;
|
||||
using NavisworksTransport.Commands;
|
||||
using NavisworksTransport.UI.WPF.Collections;
|
||||
using NavisworksTransport.Utils;
|
||||
@ -201,6 +201,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
private readonly Core.Animation.PathAnimationManager _pathAnimationManager;
|
||||
private readonly ClashDetectiveIntegration _clashIntegration;
|
||||
private readonly UIStateManager _uiStateManager;
|
||||
private PathPlanningManager _pathPlanningManager;
|
||||
|
||||
// 动画参数相关字段(从配置初始化)
|
||||
private ObservableCollection<int> _availableFrameRates;
|
||||
@ -804,6 +805,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
public ICommand ClearAnimatedObjectCommand { get; private set; }
|
||||
public ICommand EditObjectRotationCommand { get; private set; }
|
||||
public ICommand GenerateAnimationCommand { get; private set; }
|
||||
public ICommand AddToBatchQueueCommand { get; private set; }
|
||||
public ICommand ApplyManualTargetsFromSelectionCommand { get; private set; }
|
||||
public ICommand ClearManualTargetsCommand { get; private set; }
|
||||
public ICommand RemoveManualTargetCommand { get; private set; }
|
||||
@ -888,6 +890,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共方法
|
||||
|
||||
/// <summary>
|
||||
/// 设置PathPlanningManager(用于批处理队列功能)
|
||||
/// </summary>
|
||||
public void SetPathPlanningManager(PathPlanningManager pathPlanningManager)
|
||||
{
|
||||
_pathPlanningManager = pathPlanningManager;
|
||||
LogManager.Info("AnimationControlViewModel已设置PathPlanningManager");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初始化方法
|
||||
|
||||
/// <summary>
|
||||
@ -956,6 +971,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject);
|
||||
EditObjectRotationCommand = new RelayCommand(ExecuteEditObjectRotation, () => HasSelectedAnimatedObject);
|
||||
GenerateAnimationCommand = new RelayCommand(ExecuteGenerateAnimation, () => CanGenerateAnimation);
|
||||
AddToBatchQueueCommand = new RelayCommand(ExecuteAddToBatchQueue, () => CanGenerateAnimation);
|
||||
ApplyManualTargetsFromSelectionCommand = new RelayCommand(ExecuteApplyManualTargetsFromSelection);
|
||||
ClearManualTargetsCommand = new RelayCommand(ExecuteClearManualTargets, () => HasManualCollisionTargets);
|
||||
RemoveManualTargetCommand = new RelayCommand<ManualCollisionTargetViewModel>(ExecuteRemoveManualTarget, target => target != null);
|
||||
@ -2509,6 +2525,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
animatedObject = SelectedAnimatedObject;
|
||||
}
|
||||
|
||||
// 初始化碰撞检测缓存(在 animatedObject 声明之后)
|
||||
if (!manualModeEnabled)
|
||||
{
|
||||
ClashDetectiveIntegration.InitializeCollisionDetectionCache(animatedObject);
|
||||
}
|
||||
|
||||
if (manualModeEnabled)
|
||||
{
|
||||
_pathAnimationManager.SetManualCollisionTargets(manualTargets, true);
|
||||
@ -3456,6 +3478,78 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加到批处理队列
|
||||
/// </summary>
|
||||
private async void ExecuteAddToBatchQueue()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CanGenerateAnimation)
|
||||
{
|
||||
UpdateMainStatus("无法添加到批处理:缺少必要条件");
|
||||
LogManager.Warning("尝试添加到批处理但条件不满足");
|
||||
return;
|
||||
}
|
||||
|
||||
LogManager.Info($"[批处理] 添加路径到批处理队列: {CurrentPathRoute.Name}");
|
||||
|
||||
// 创建批处理队列项
|
||||
var queueItem = new BatchQueueItem
|
||||
{
|
||||
PathRouteId = CurrentPathRoute.Id,
|
||||
PathRouteName = CurrentPathRoute.Name,
|
||||
Status = BatchQueueStatus.Pending,
|
||||
CreatedTime = DateTime.Now,
|
||||
|
||||
// 动画配置
|
||||
FrameRate = _animationFrameRate,
|
||||
DurationSeconds = AnimationDuration,
|
||||
|
||||
// 碰撞检测配置
|
||||
DetectionGapMeters = _detectionGap,
|
||||
|
||||
// 运动物体配置
|
||||
IsVirtualVehicle = UseVirtualVehicle,
|
||||
VirtualVehicleLength = VirtualVehicleLength,
|
||||
VirtualVehicleWidth = VirtualVehicleWidth,
|
||||
VirtualVehicleHeight = VirtualVehicleHeight,
|
||||
VehicleObjectId = UseVirtualVehicle ? null : SelectedAnimatedObject?.ToString(),
|
||||
VehicleObjectName = UseVirtualVehicle ? null : SelectedAnimatedObject?.DisplayName,
|
||||
|
||||
// 被检测项配置
|
||||
DetectAllObjects = !IsManualCollisionTargetEnabled,
|
||||
DetectionItems = IsManualCollisionTargetEnabled
|
||||
? GetValidManualCollisionTargets(pruneInvalidEntries: true).Select(t => t.ToString()).ToList()
|
||||
: new List<string>()
|
||||
};
|
||||
|
||||
// 调用批处理管理器添加到队列
|
||||
var batchQueueManager = Core.BatchQueueManager.Instance;
|
||||
|
||||
// 确保BatchQueueManager有PathPlanningManager(用于获取数据库)
|
||||
if (_pathPlanningManager != null)
|
||||
{
|
||||
batchQueueManager.SetPathPlanningManager(_pathPlanningManager);
|
||||
}
|
||||
|
||||
var itemId = await batchQueueManager.AddQueueItemAsync(queueItem);
|
||||
|
||||
LogManager.Info($"[批处理] 已创建队列项: {queueItem.PathRouteName}, " +
|
||||
$"虚拟车辆: {queueItem.IsVirtualVehicle}, " +
|
||||
$"帧率: {queueItem.FrameRate}, " +
|
||||
$"时长: {queueItem.DurationSeconds:F2}秒, " +
|
||||
$"ID: {itemId}");
|
||||
|
||||
UpdateMainStatus($"已添加到批处理队列: {queueItem.PathRouteName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"添加到批处理失败: {ex.Message}", ex);
|
||||
UpdateMainStatus($"添加失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -6,29 +6,28 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using NavisworksTransport.Core;
|
||||
using NavisworksTransport.Core.Collision;
|
||||
using NavisworksTransport.Core.Models;
|
||||
using NavisworksTransport.Utils;
|
||||
using NavisworksTransport.UI.WPF.Commands;
|
||||
using NavisworksTransport.Commands;
|
||||
using NavisworksTransport.UI.WPF.Views;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 批处理任务管理ViewModel
|
||||
/// 批处理队列管理ViewModel - 简化的FIFO队列界面
|
||||
/// </summary>
|
||||
public class BatchTaskManagementViewModel : ViewModelBase
|
||||
{
|
||||
#region 字段
|
||||
|
||||
private PathDatabase _database;
|
||||
private BatchTaskManager _taskManager;
|
||||
private BatchCollisionSummaryReportGenerator _reportGenerator;
|
||||
private PathPlanningManager _pathPlanningManager;
|
||||
private BatchQueueManager _queueManager;
|
||||
private readonly UIStateManager _uiStateManager;
|
||||
|
||||
private ObservableCollection<BatchCollisionTask> _tasks;
|
||||
private BatchCollisionTask _selectedTask;
|
||||
private BatchTaskStatus? _statusFilter;
|
||||
private ObservableCollection<BatchQueueItem> _queueItems;
|
||||
private BatchQueueItem _selectedItem;
|
||||
private BatchQueueStatus? _statusFilter;
|
||||
private string _progressText;
|
||||
private bool _isExecuting;
|
||||
|
||||
@ -37,34 +36,34 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
#region 属性
|
||||
|
||||
/// <summary>
|
||||
/// 任务列表
|
||||
/// 队列项列表
|
||||
/// </summary>
|
||||
public ObservableCollection<BatchCollisionTask> Tasks
|
||||
public ObservableCollection<BatchQueueItem> QueueItems
|
||||
{
|
||||
get => _tasks;
|
||||
set => SetProperty(ref _tasks, value);
|
||||
get => _queueItems;
|
||||
set => SetProperty(ref _queueItems, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中的任务
|
||||
/// 选中的队列项
|
||||
/// </summary>
|
||||
public BatchCollisionTask SelectedTask
|
||||
public BatchQueueItem SelectedItem
|
||||
{
|
||||
get => _selectedTask;
|
||||
set => SetProperty(ref _selectedTask, value);
|
||||
get => _selectedItem;
|
||||
set => SetProperty(ref _selectedItem, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态筛选器
|
||||
/// </summary>
|
||||
public BatchTaskStatus? StatusFilter
|
||||
public BatchQueueStatus? StatusFilter
|
||||
{
|
||||
get => _statusFilter;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _statusFilter, value))
|
||||
{
|
||||
_ = LoadTasksAsync();
|
||||
_ = LoadQueueItemsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -88,34 +87,40 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以执行任务
|
||||
/// 是否可以执行队列
|
||||
/// </summary>
|
||||
public bool CanExecuteTask => SelectedTask != null && SelectedTask.Status == BatchTaskStatus.Pending && !IsExecuting;
|
||||
public bool CanExecuteQueue => !IsExecuting && HasPendingItems;
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以取消任务
|
||||
/// 是否可以取消当前任务
|
||||
/// </summary>
|
||||
public bool CanCancelTask => SelectedTask != null && SelectedTask.Status == BatchTaskStatus.Running;
|
||||
public bool CanCancelCurrent => IsExecuting;
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以删除任务
|
||||
/// 是否可以删除选中项
|
||||
/// </summary>
|
||||
public bool CanDeleteTask => SelectedTask != null &&
|
||||
(SelectedTask.Status == BatchTaskStatus.Pending || SelectedTask.Status == BatchTaskStatus.Completed || SelectedTask.Status == BatchTaskStatus.Failed);
|
||||
public bool CanDeleteItem => SelectedItem != null &&
|
||||
(SelectedItem.Status == BatchQueueStatus.Pending ||
|
||||
SelectedItem.Status == BatchQueueStatus.Completed ||
|
||||
SelectedItem.Status == BatchQueueStatus.Failed);
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以查看报告
|
||||
/// </summary>
|
||||
public bool CanViewReport => SelectedTask != null && SelectedTask.Status == BatchTaskStatus.Completed;
|
||||
public bool CanViewReport => SelectedItem != null && SelectedItem.Status == BatchQueueStatus.Completed;
|
||||
|
||||
/// <summary>
|
||||
/// 是否有待处理项
|
||||
/// </summary>
|
||||
public bool HasPendingItems => QueueItems?.Any(item => item.Status == BatchQueueStatus.Pending) ?? false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand CreateTaskCommand { get; }
|
||||
public ICommand ExecuteTaskCommand { get; }
|
||||
public ICommand CancelTaskCommand { get; }
|
||||
public ICommand DeleteTaskCommand { get; }
|
||||
public ICommand ExecuteQueueCommand { get; }
|
||||
public ICommand CancelCurrentCommand { get; }
|
||||
public ICommand DeleteItemCommand { get; }
|
||||
public ICommand ViewReportCommand { get; }
|
||||
public ICommand RefreshCommand { get; }
|
||||
|
||||
@ -127,27 +132,23 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
// 不在构造函数中访问BatchTaskManager,因为此时PathDatabase可能还未初始化
|
||||
_database = null;
|
||||
_taskManager = null;
|
||||
_pathPlanningManager = null;
|
||||
_queueManager = null;
|
||||
_uiStateManager = UIStateManager.Instance;
|
||||
_reportGenerator = null;
|
||||
|
||||
_tasks = new ObservableCollection<BatchCollisionTask>();
|
||||
_queueItems = new ObservableCollection<BatchQueueItem>();
|
||||
|
||||
// 初始化命令
|
||||
CreateTaskCommand = new RelayCommand(async () => await CreateTaskAsync(), () => !IsExecuting);
|
||||
ExecuteTaskCommand = new RelayCommand(async () => await ExecuteTaskAsync(), () => CanExecuteTask);
|
||||
CancelTaskCommand = new RelayCommand(CancelTask, () => CanCancelTask);
|
||||
DeleteTaskCommand = new RelayCommand(async () => await DeleteTaskAsync(), () => CanDeleteTask);
|
||||
ExecuteQueueCommand = new RelayCommand(async () => await ExecuteQueueAsync(), () => CanExecuteQueue);
|
||||
CancelCurrentCommand = new RelayCommand(CancelCurrent, () => CanCancelCurrent);
|
||||
DeleteItemCommand = new RelayCommand(async () => await DeleteItemAsync(), () => CanDeleteItem);
|
||||
ViewReportCommand = new RelayCommand(async () => await ViewReportAsync(), () => CanViewReport);
|
||||
RefreshCommand = new RelayCommand(async () => await LoadTasksAsync(), () => !IsExecuting);
|
||||
RefreshCommand = new RelayCommand(async () => await LoadQueueItemsAsync(), () => !IsExecuting);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"BatchTaskManagementViewModel构造函数异常: {ex.Message}", ex);
|
||||
// 确保对象处于可用状态,即使初始化失败
|
||||
_tasks = new ObservableCollection<BatchCollisionTask>();
|
||||
_queueItems = new ObservableCollection<BatchQueueItem>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,62 +157,83 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
#region 公共方法
|
||||
|
||||
/// <summary>
|
||||
/// 设置BatchTaskManager(由LogisticsControlPanel在PathDatabase初始化后调用)
|
||||
/// 设置PathPlanningManager(由LogisticsControlPanel在初始化时调用)
|
||||
/// </summary>
|
||||
public void SetBatchTaskManager(PathDatabase database)
|
||||
public void SetPathPlanningManager(PathPlanningManager pathPlanningManager)
|
||||
{
|
||||
try
|
||||
{
|
||||
_database = database;
|
||||
_taskManager = BatchTaskManager.Instance;
|
||||
_reportGenerator = new BatchCollisionSummaryReportGenerator(_database);
|
||||
_pathPlanningManager = pathPlanningManager;
|
||||
_queueManager = BatchQueueManager.Instance;
|
||||
|
||||
// 订阅任务管理器事件
|
||||
if (_taskManager != null)
|
||||
// 订阅队列管理器事件
|
||||
if (_queueManager != null)
|
||||
{
|
||||
_taskManager.TaskStarted += OnTaskStarted;
|
||||
_taskManager.TaskCompleted += OnTaskCompleted;
|
||||
_taskManager.TaskProgress += OnTaskProgress;
|
||||
_queueManager.ItemStarted += OnItemStarted;
|
||||
_queueManager.ItemCompleted += OnItemCompleted;
|
||||
_queueManager.ItemProgress += OnItemProgress;
|
||||
}
|
||||
|
||||
// 加载任务列表
|
||||
_ = LoadTasksAsync();
|
||||
// 加载队列列表
|
||||
_ = LoadQueueItemsAsync();
|
||||
|
||||
LogManager.Info("[BatchTaskManagement] BatchTaskManager已设置");
|
||||
LogManager.Info("[BatchQueueManagement] PathPlanningManager已设置");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[BatchTaskManagement] 设置BatchTaskManager失败: {ex.Message}");
|
||||
LogManager.Error($"[BatchQueueManagement] 设置PathPlanningManager失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载任务列表
|
||||
/// 获取数据库(延迟加载)
|
||||
/// </summary>
|
||||
public async Task LoadTasksAsync()
|
||||
private PathDatabase GetDatabase()
|
||||
{
|
||||
return _pathPlanningManager?.GetPathDatabase();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载队列项列表
|
||||
/// </summary>
|
||||
public async Task LoadQueueItemsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_taskManager == null)
|
||||
var database = GetDatabase();
|
||||
if (database == null)
|
||||
{
|
||||
LogManager.Warning("[BatchTaskManagement] BatchTaskManager未初始化");
|
||||
LogManager.Warning("[BatchQueueManagement] Database未初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
var tasks = await _taskManager.GetBatchTasksAsync(StatusFilter, limit: 100);
|
||||
var items = await database.GetBatchQueueItemsAsync(statusFilter: StatusFilter, limit: 1000);
|
||||
|
||||
// 应用筛选
|
||||
if (StatusFilter.HasValue)
|
||||
{
|
||||
items = items.Where(item => item.Status == StatusFilter.Value).ToList();
|
||||
}
|
||||
|
||||
// 按创建时间倒序
|
||||
items = items.OrderByDescending(item => item.CreatedTime).ToList();
|
||||
|
||||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||||
{
|
||||
Tasks.Clear();
|
||||
foreach (var task in tasks)
|
||||
QueueItems.Clear();
|
||||
foreach (var item in items)
|
||||
{
|
||||
Tasks.Add(task);
|
||||
QueueItems.Add(item);
|
||||
}
|
||||
|
||||
// 通知属性变化以更新按钮状态
|
||||
OnPropertyChanged(nameof(HasPendingItems));
|
||||
OnPropertyChanged(nameof(CanExecuteQueue));
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[BatchTaskManagement] 加载任务列表失败: {ex.Message}");
|
||||
LogManager.Error($"[BatchQueueManagement] 加载队列列表失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,84 +242,56 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
#region 私有方法
|
||||
|
||||
/// <summary>
|
||||
/// 创建任务
|
||||
/// 执行队列
|
||||
/// </summary>
|
||||
private async Task CreateTaskAsync()
|
||||
private async Task ExecuteQueueAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dialog = new CreateBatchTaskDialog();
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
var taskConfig = dialog.GetTaskConfig();
|
||||
|
||||
if (_taskManager != null)
|
||||
{
|
||||
var taskId = await _taskManager.CreateBatchTaskAsync(
|
||||
taskConfig.Name,
|
||||
taskConfig.Description,
|
||||
taskConfig.ItemConfigs
|
||||
);
|
||||
if (_queueManager == null) return;
|
||||
|
||||
LogManager.Info($"[BatchTaskManagement] 已创建批处理任务,ID: {taskId}");
|
||||
await LoadTasksAsync();
|
||||
}
|
||||
await _queueManager.ProcessQueueAsync();
|
||||
LogManager.Info("[BatchQueueManagement] 已开始执行队列");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[BatchQueueManagement] 执行队列失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消当前任务
|
||||
/// </summary>
|
||||
private void CancelCurrent()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_queueManager != null)
|
||||
{
|
||||
_queueManager.CancelCurrentItem();
|
||||
LogManager.Info("[BatchQueueManagement] 已取消当前任务");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[BatchTaskManagement] 创建任务失败: {ex.Message}");
|
||||
LogManager.Error($"[BatchQueueManagement] 取消任务失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行任务
|
||||
/// 删除队列项
|
||||
/// </summary>
|
||||
private async Task ExecuteTaskAsync()
|
||||
private async Task DeleteItemAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (SelectedTask == null || _taskManager == null) return;
|
||||
if (SelectedItem == null) return;
|
||||
|
||||
_taskManager.EnqueueTask(SelectedTask.Id);
|
||||
LogManager.Info($"[BatchTaskManagement] 已加入执行队列,任务ID: {SelectedTask.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[BatchTaskManagement] 执行任务失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消任务
|
||||
/// </summary>
|
||||
private void CancelTask()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_taskManager != null)
|
||||
{
|
||||
_taskManager.CancelCurrentTask();
|
||||
LogManager.Info("[BatchTaskManagement] 已取消当前任务");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[BatchTaskManagement] 取消任务失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除任务
|
||||
/// </summary>
|
||||
private async Task DeleteTaskAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (SelectedTask == null || _taskManager == null) return;
|
||||
var database = GetDatabase();
|
||||
if (database == null) return;
|
||||
|
||||
var result = MessageBox.Show(
|
||||
$"确定要删除任务\"{SelectedTask.Name}\"吗?",
|
||||
$"确定要删除队列项\"{SelectedItem.PathRouteName}\"吗?",
|
||||
"确认删除",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question
|
||||
@ -305,38 +299,43 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
await _taskManager.DeleteBatchTaskAsync(SelectedTask.Id);
|
||||
LogManager.Info($"[BatchTaskManagement] 已删除任务,ID: {SelectedTask.Id}");
|
||||
await LoadTasksAsync();
|
||||
await database.DeleteBatchQueueItemAsync(SelectedItem.Id);
|
||||
LogManager.Info($"[BatchQueueManagement] 已删除队列项,ID: {SelectedItem.Id}");
|
||||
await LoadQueueItemsAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[BatchTaskManagement] 删除任务失败: {ex.Message}");
|
||||
LogManager.Error($"[BatchQueueManagement] 删除队列项失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查看报告
|
||||
/// 查看报告 - 复用碰撞历史列表中的报告按钮代码
|
||||
/// </summary>
|
||||
private async Task ViewReportAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (SelectedTask == null || _reportGenerator == null) return;
|
||||
if (SelectedItem == null || string.IsNullOrEmpty(SelectedItem.ClashDetectiveTestName))
|
||||
{
|
||||
MessageBox.Show("未找到碰撞检测结果", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var html = await _reportGenerator.GenerateSummaryReportHtmlAsync(SelectedTask.Id);
|
||||
|
||||
// 保存到临时文件并打开
|
||||
var tempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"BatchTaskReport_{SelectedTask.Id}.html");
|
||||
System.IO.File.WriteAllText(tempPath, html);
|
||||
|
||||
System.Diagnostics.Process.Start(tempPath);
|
||||
LogManager.Info($"[BatchTaskManagement] 已打开报告,任务ID: {SelectedTask.Id}");
|
||||
var testName = SelectedItem.ClashDetectiveTestName;
|
||||
LogManager.Info($"[批处理队列] 开始生成历史碰撞报告: {testName}");
|
||||
|
||||
// 复用碰撞历史列表中的报告按钮代码
|
||||
var command = GenerateCollisionReportCommand.CreateComprehensive(autoHighlight: false);
|
||||
command.SetTestName(testName);
|
||||
await command.ExecuteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[BatchTaskManagement] 查看报告失败: {ex.Message}");
|
||||
LogManager.Error($"[批处理队列] 生成历史报告失败: {ex.Message}", ex);
|
||||
MessageBox.Show($"生成历史报告失败: {ex.Message}", "错误",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,26 +343,26 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
#region 事件处理
|
||||
|
||||
private async void OnTaskStarted(object sender, BatchTaskEventArgs e)
|
||||
private async void OnItemStarted(object sender, BatchQueueItemEventArgs e)
|
||||
{
|
||||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||||
{
|
||||
IsExecuting = true;
|
||||
ProgressText = $"正在执行任务: {e.Task.Name}";
|
||||
ProgressText = $"正在执行: {e.Item.PathRouteName}";
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnTaskCompleted(object sender, BatchTaskEventArgs e)
|
||||
private async void OnItemCompleted(object sender, BatchQueueItemEventArgs e)
|
||||
{
|
||||
await _uiStateManager.ExecuteUIUpdateAsync(async () =>
|
||||
{
|
||||
IsExecuting = false;
|
||||
ProgressText = $"任务完成: {e.Task.Name}";
|
||||
await LoadTasksAsync();
|
||||
ProgressText = $"完成: {e.Item.PathRouteName} - 碰撞数: {e.Item.CollisionCount ?? 0}";
|
||||
await LoadQueueItemsAsync();
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnTaskProgress(object sender, BatchTaskProgressEventArgs e)
|
||||
private async void OnItemProgress(object sender, BatchQueueProgressEventArgs e)
|
||||
{
|
||||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||||
{
|
||||
|
||||
@ -334,7 +334,12 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
|
||||
<Button Content="生成动画"
|
||||
Command="{Binding GenerateAnimationCommand}"
|
||||
IsEnabled="{Binding CanGenerateAnimation}"
|
||||
Style="{StaticResource SecondaryButtonStyle}"/>
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Content="添加到批处理"
|
||||
Command="{Binding AddToBatchQueueCommand}"
|
||||
IsEnabled="{Binding CanGenerateAnimation}"
|
||||
Style="{StaticResource ActionButtonStyle}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 生成状态提示 -->
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<!--
|
||||
NavisworksTransport 批处理任务管理页签视图 - 采用与其他页签一致的Navisworks 2026风格
|
||||
NavisworksTransport 批处理队列管理页签视图 - 采用与其他页签一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
1. 任务列表:显示所有批处理任务,支持状态筛选
|
||||
2. 任务操作:创建、执行、取消、删除任务
|
||||
3. 任务监控:实时显示任务执行进度
|
||||
4. 报告查看:查看汇总报告和单独报告
|
||||
1. 队列列表:显示所有批处理队列项,支持状态筛选
|
||||
2. 队列操作:执行队列、取消当前、删除项
|
||||
3. 队列监控:实时显示队列执行进度
|
||||
4. 报告查看:查看单项碰撞报告
|
||||
|
||||
设计原则:与Navisworks 2026风格一致,480像素宽度,现代化UI布局,采用Border分组
|
||||
-->
|
||||
@ -27,8 +27,9 @@ NavisworksTransport 批处理任务管理页签视图 - 采用与其他页签一
|
||||
|
||||
<!-- 转换器资源 -->
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<converters:BatchQueueStatusConverter x:Key="BatchQueueStatusConverter"/>
|
||||
|
||||
<!-- 批处理任务管理页面特有的样式 -->
|
||||
<!-- 批处理队列管理页面特有的样式 -->
|
||||
<Style x:Key="InfoTextStyle" TargetType="Label">
|
||||
<Setter Property="FontSize" Value="10"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource NavisworksTextBrush}"/>
|
||||
@ -39,29 +40,36 @@ NavisworksTransport 批处理任务管理页签视图 - 采用与其他页签一
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="10">
|
||||
<StackPanel>
|
||||
<!-- 区域1: 任务操作 -->
|
||||
<!-- 区域1: 队列操作 -->
|
||||
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,0,0,15" Padding="12">
|
||||
<StackPanel>
|
||||
<Label Content="批处理任务管理" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
<Label Content="批处理队列管理" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,10">
|
||||
<Button Content="创建任务"
|
||||
Command="{Binding CreateTaskCommand}"
|
||||
<Button Content="执行队列"
|
||||
Command="{Binding ExecuteQueueCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"/>
|
||||
<Button Content="执行任务"
|
||||
Command="{Binding ExecuteTaskCommand}"
|
||||
<Button Content="取消当前"
|
||||
Command="{Binding CancelCurrentCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"/>
|
||||
<Button Content="取消任务"
|
||||
Command="{Binding CancelTaskCommand}"
|
||||
<Button Content="删除选中"
|
||||
Command="{Binding DeleteItemCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"/>
|
||||
<Button Content="删除任务"
|
||||
Command="{Binding DeleteTaskCommand}"
|
||||
<Button Content="查看报告"
|
||||
Command="{Binding ViewReportCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"/>
|
||||
<Button Content="刷新"
|
||||
Command="{Binding RefreshCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 说明文本 -->
|
||||
<TextBlock Text="提示:在动画控制页面点击"添加到批处理"按钮可添加队列项"
|
||||
FontSize="10"
|
||||
Foreground="{StaticResource NavisworksTextBrush}"
|
||||
Margin="0,5,0,0"
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@ -77,27 +85,32 @@ NavisworksTransport 批处理任务管理页签视图 - 采用与其他页签一
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" Content="任务状态:" Style="{StaticResource ParameterLabelStyle}" Width="80"/>
|
||||
<Label Grid.Column="0" Content="队列状态:" Style="{StaticResource ParameterLabelStyle}" Width="80"/>
|
||||
<ComboBox Grid.Column="1"
|
||||
ItemsSource="{Binding StatusFilterOptions}"
|
||||
ItemsSource="{x:Static converters:BatchQueueStatusHelper.AllStatuses}"
|
||||
SelectedItem="{Binding StatusFilter}"
|
||||
DisplayMemberPath="DisplayName"
|
||||
Width="200"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="5,0,0,0"
|
||||
ToolTip="选择要显示的任务状态"/>
|
||||
ToolTip="选择要显示的队列状态">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource BatchQueueStatusConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 区域3: 任务列表 -->
|
||||
<!-- 区域3: 队列列表 -->
|
||||
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,0,0,15" Padding="12">
|
||||
<StackPanel>
|
||||
<Label Content="任务列表" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
<Label Content="队列列表" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
|
||||
<!-- 任务列表表格 -->
|
||||
<DataGrid ItemsSource="{Binding Tasks}"
|
||||
SelectedItem="{Binding SelectedTask}"
|
||||
<!-- 队列列表表格 -->
|
||||
<DataGrid ItemsSource="{Binding QueueItems}"
|
||||
SelectedItem="{Binding SelectedItem}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
@ -107,9 +120,9 @@ NavisworksTransport 批处理任务管理页签视图 - 采用与其他页签一
|
||||
Margin="0,10,0,0"
|
||||
Height="300">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="名称" Binding="{Binding Name}" Width="*"/>
|
||||
<DataGridTextColumn Header="状态" Binding="{Binding Status}" Width="80"/>
|
||||
<DataGridTextColumn Header="进度" Binding="{Binding ProgressDisplay}" Width="100"/>
|
||||
<DataGridTextColumn Header="路径名称" Binding="{Binding PathRouteName}" Width="*"/>
|
||||
<DataGridTextColumn Header="状态" Binding="{Binding Status, Converter={StaticResource BatchQueueStatusConverter}}" Width="80"/>
|
||||
<DataGridTextColumn Header="碰撞数" Binding="{Binding CollisionCount, StringFormat=\{0\}}" Width="60"/>
|
||||
<DataGridTextColumn Header="创建时间" Binding="{Binding CreatedTime, StringFormat=yyyy-MM-dd HH:mm}" Width="120"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
@ -127,14 +140,6 @@ NavisworksTransport 批处理任务管理页签视图 - 采用与其他页签一
|
||||
Foreground="{StaticResource NavisworksTextBrush}"
|
||||
Margin="0,10,0,10"
|
||||
TextWrapping="Wrap"/>
|
||||
|
||||
<!-- 查看报告按钮 -->
|
||||
<Button Content="查看汇总报告"
|
||||
Command="{Binding ViewReportCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
Width="150"
|
||||
HorizontalAlignment="Left"
|
||||
Visibility="{Binding CanViewReport, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
@ -72,6 +72,8 @@
|
||||
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
|
||||
<Button Content="添加路径" Name="btnAddPath" Click="btnAddPath_Click"
|
||||
Style="{StaticResource ActionButtonStyle}" Width="100" Margin="0,0,5,0"/>
|
||||
<Button Content="配置" Name="btnConfigPath" Click="btnConfigPath_Click"
|
||||
Style="{StaticResource ActionButtonStyle}" Width="80" Margin="0,0,5,0"/>
|
||||
<Button Content="移除路径" Name="btnRemovePath" Click="btnRemovePath_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}" Width="100" Margin="0,0,5,0"/>
|
||||
<Button Content="上移" Name="btnMoveUp" Click="btnMoveUp_Click"
|
||||
@ -81,7 +83,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<DataGrid Grid.Row="1"
|
||||
<DataGrid Grid.Row="1"
|
||||
Name="dgPaths"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
@ -93,8 +95,10 @@
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="序号" Binding="{Binding SequenceNumber}" Width="50" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="路径名称" Binding="{Binding PathRouteName}" Width="*"/>
|
||||
<DataGridCheckBoxColumn Header="使用虚拟车辆" Binding="{Binding IsVirtualVehicle}" Width="100"/>
|
||||
<DataGridTextColumn Header="车辆尺寸" Binding="{Binding VehicleSizeText}" Width="120"/>
|
||||
<DataGridTextColumn Header="物流类型" Binding="{Binding VehicleTypeDisplay}" Width="100"/>
|
||||
<DataGridTextColumn Header="物流信息" Binding="{Binding VehicleInfoText}" Width="200"/>
|
||||
<DataGridTextColumn Header="被检测项" Binding="{Binding DetectionItemsText}" Width="100"/>
|
||||
<DataGridTextColumn Header="配置" Binding="{Binding ConfigStatusText}" Width="80"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
|
||||
@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using NavisworksTransport.Core;
|
||||
using NavisworksTransport.Core.Config;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Views
|
||||
@ -50,19 +51,111 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 显示路径选择对话框
|
||||
// 这里简化处理,直接添加示例路径
|
||||
var config = new BatchTaskItemConfigUI
|
||||
try
|
||||
{
|
||||
PathRouteId = "sample_path_" + DateTime.Now.Ticks,
|
||||
PathRouteName = "示例路径 " + (_pathConfigs.Count + 1),
|
||||
SequenceNumber = _pathConfigs.Count,
|
||||
IsVirtualVehicle = true,
|
||||
ConfigOverride = null
|
||||
};
|
||||
|
||||
_pathConfigs.Add(config);
|
||||
UpdateSequenceNumbers();
|
||||
// 显示路径选择对话框
|
||||
var pathDialog = new PathSelectionDialog();
|
||||
if (pathDialog.ShowDialog() == true)
|
||||
{
|
||||
var selectedPaths = pathDialog.GetSelectedPaths();
|
||||
|
||||
// 从系统配置读取虚拟车辆默认值
|
||||
var systemConfig = ConfigManager.Instance.Current;
|
||||
|
||||
foreach (var path in selectedPaths)
|
||||
{
|
||||
var config = new BatchTaskItemConfigUI
|
||||
{
|
||||
PathRouteId = path.Id,
|
||||
PathRouteName = path.Name,
|
||||
SequenceNumber = _pathConfigs.Count,
|
||||
IsVirtualVehicle = true, // 默认使用虚拟车辆
|
||||
VirtualVehicleLength = systemConfig.PathEditing.VehicleLengthMeters,
|
||||
VirtualVehicleWidth = systemConfig.PathEditing.VehicleWidthMeters,
|
||||
VirtualVehicleHeight = systemConfig.PathEditing.VehicleHeightMeters,
|
||||
ConfigOverride = null
|
||||
};
|
||||
|
||||
_pathConfigs.Add(config);
|
||||
}
|
||||
|
||||
UpdateSequenceNumbers();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"添加路径失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置路径
|
||||
/// </summary>
|
||||
private void btnConfigPath_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (dgPaths.SelectedItem is BatchTaskItemConfigUI selected)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogManager.Info($"[CreateBatchTaskDialog] 开始配置路径: {selected.PathRouteName}");
|
||||
|
||||
// 创建配置对话框,传入当前配置
|
||||
var configDialog = new PathConfigDialog(selected.ConfigOverride);
|
||||
|
||||
// 设置当前值
|
||||
configDialog.rbVirtualVehicle.IsChecked = selected.IsVirtualVehicle;
|
||||
configDialog.rbRealObject.IsChecked = !selected.IsVirtualVehicle;
|
||||
configDialog.txtVehicleLength.Text = selected.VirtualVehicleLength.ToString("F2");
|
||||
configDialog.txtVehicleWidth.Text = selected.VirtualVehicleWidth.ToString("F2");
|
||||
configDialog.txtVehicleHeight.Text = selected.VirtualVehicleHeight.ToString("F2");
|
||||
|
||||
if (!string.IsNullOrEmpty(selected.VehicleObjectName))
|
||||
{
|
||||
configDialog.txtSelectedObject.Text = selected.VehicleObjectName;
|
||||
configDialog.txtObjectId.Text = selected.VehicleObjectId;
|
||||
}
|
||||
|
||||
if (selected.DetectionItems != null && selected.DetectionItems.Count > 0)
|
||||
{
|
||||
configDialog.txtDetectionItemCount.Text = selected.DetectionItems.Count.ToString();
|
||||
configDialog.rbDetectSelected.IsChecked = true;
|
||||
}
|
||||
|
||||
if (selected.ConfigOverride != null)
|
||||
{
|
||||
configDialog.cbOverrideConfig.IsChecked = true;
|
||||
}
|
||||
|
||||
if (configDialog.ShowDialog() == true)
|
||||
{
|
||||
var result = configDialog.GetResult();
|
||||
|
||||
// 更新配置
|
||||
selected.IsVirtualVehicle = result.IsVirtualVehicle;
|
||||
selected.VirtualVehicleLength = result.VirtualVehicleLength;
|
||||
selected.VirtualVehicleWidth = result.VirtualVehicleWidth;
|
||||
selected.VirtualVehicleHeight = result.VirtualVehicleHeight;
|
||||
selected.VehicleObjectId = result.VehicleObjectId;
|
||||
selected.VehicleObjectName = result.VehicleObjectName;
|
||||
selected.DetectionItems = result.DetectionItems;
|
||||
selected.ConfigOverride = result.ConfigOverride;
|
||||
|
||||
// 刷新显示
|
||||
dgPaths.Items.Refresh();
|
||||
|
||||
LogManager.Info($"[CreateBatchTaskDialog] 路径配置完成: {selected.PathRouteName}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[CreateBatchTaskDialog] 配置路径失败: {ex.Message}\n{ex.StackTrace}");
|
||||
MessageBox.Show($"配置路径失败: {ex.Message}\n\n堆栈跟踪:\n{ex.StackTrace}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("请先选择一条路径", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -162,6 +162,13 @@ namespace NavisworksTransport.UI.WPF
|
||||
// 更新引用
|
||||
AnimationControlView = newAnimationControlView;
|
||||
|
||||
// 设置PathPlanningManager(用于批处理队列功能)
|
||||
if (AnimationControlView?.ViewModel != null && _pathPlanningManager != null)
|
||||
{
|
||||
AnimationControlView.ViewModel.SetPathPlanningManager(_pathPlanningManager);
|
||||
LogManager.Info("AnimationControlView初始化完成 - 已设置PathPlanningManager");
|
||||
}
|
||||
|
||||
// 初始化时同步车辆参数
|
||||
SyncVehicleParametersToAnimationView();
|
||||
|
||||
@ -260,19 +267,11 @@ namespace NavisworksTransport.UI.WPF
|
||||
|
||||
_batchTaskManagementView = newBatchTaskManagementView;
|
||||
|
||||
// 设置BatchTaskManager(类似PathEditingView的设置方式)
|
||||
// 设置PathPlanningManager(类似PathEditingView的设置方式)
|
||||
if (_batchTaskManagementView?.ViewModel != null && _pathPlanningManager != null)
|
||||
{
|
||||
var database = _pathPlanningManager.GetPathDatabase();
|
||||
if (database != null)
|
||||
{
|
||||
_batchTaskManagementView.ViewModel.SetBatchTaskManager(database);
|
||||
LogManager.Info("BatchTaskManagementView初始化完成 - 已设置BatchTaskManager");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Info("BatchTaskManagementView初始化完成 - PathDatabase未初始化,BatchTaskManager将在文档加载后设置");
|
||||
}
|
||||
_batchTaskManagementView.ViewModel.SetPathPlanningManager(_pathPlanningManager);
|
||||
LogManager.Info("BatchTaskManagementView初始化完成 - 已设置PathPlanningManager");
|
||||
}
|
||||
|
||||
LogManager.Info("BatchTaskManagementView初始化完成");
|
||||
|
||||
182
src/UI/WPF/Views/PathConfigDialog.xaml
Normal file
182
src/UI/WPF/Views/PathConfigDialog.xaml
Normal file
@ -0,0 +1,182 @@
|
||||
<!--
|
||||
路径配置对话框
|
||||
|
||||
功能说明:
|
||||
1. 选择车辆类型(虚拟车辆或真实物体)
|
||||
2. 配置虚拟车辆尺寸
|
||||
3. 选择真实物体(支持从视图或选择树中选择)
|
||||
4. 选择被检测项(可选)
|
||||
5. 配置覆盖参数(帧率、时长、检测间隙)
|
||||
-->
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.PathConfigDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
Title="路径配置"
|
||||
Height="700"
|
||||
Width="750"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 物流类型选择 -->
|
||||
<Border Grid.Row="0" BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Padding="12" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<Label Content="物流类型" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||
<RadioButton Name="rbVirtualVehicle" Content="使用虚拟物体" Margin="0,0,20,0" Checked="rbVirtualVehicle_Checked"/>
|
||||
<RadioButton Name="rbRealObject" Content="使用真实物体" Checked="rbRealObject_Checked"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 虚拟物体参数 -->
|
||||
<Border Grid.Row="1" BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Padding="12" Margin="0,0,0,10"
|
||||
Name="borderVirtualVehicle">
|
||||
<StackPanel>
|
||||
<Label Content="虚拟物体尺寸(米)" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="长度(X轴):" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Name="txtVehicleLength" Text="1.0" Margin="0,0,5,5"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Text="米" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="宽度(Y轴):" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Name="txtVehicleWidth" Text="1.0" Margin="0,0,5,5"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="米" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="高度(Z轴):" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Name="txtVehicleHeight" Text="2.0" Margin="0,0,5,5"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="2" Text="米" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 真实物体选择 -->
|
||||
<Border Grid.Row="2" BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Padding="12" Margin="0,0,0,10"
|
||||
Name="borderRealObject" Visibility="Collapsed">
|
||||
<StackPanel>
|
||||
<Label Content="选择运动物体" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="已选择物体:" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Name="txtSelectedObject" IsReadOnly="True"
|
||||
Text="(未选择)" Margin="0,0,5,5"/>
|
||||
<Button Grid.Row="0" Grid.Column="2" Name="btnSelectFromView" Content="从视图选择"
|
||||
Click="btnSelectFromView_Click" Style="{StaticResource ActionButtonStyle}" Margin="0,0,0,5"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="物体ID:" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Name="txtObjectId" Text="-" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 被检测项选择 -->
|
||||
<Border Grid.Row="3" BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Padding="12" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<Label Content="被检测项(可选)" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="已选择项数:" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Name="txtDetectionItemCount" Text="0" VerticalAlignment="Center" Margin="0,0,5,5"/>
|
||||
<Button Grid.Row="0" Grid.Column="2" Name="btnSelectDetectionItems" Content="选择检测项"
|
||||
Click="btnSelectDetectionItems_Click" Style="{StaticResource ActionButtonStyle}" Margin="0,0,0,5"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="检测模式:" VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Margin="0,0,0,5">
|
||||
<RadioButton Name="rbDetectAll" Content="检测所有物体" IsChecked="True" Margin="0,0,20,0"/>
|
||||
<RadioButton Name="rbDetectSelected" Content="仅检测选中项"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 配置覆盖 -->
|
||||
<Border Grid.Row="4" BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Padding="12" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<Label Content="配置覆盖(可选)" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
<CheckBox Name="cbOverrideConfig" Content="使用自定义配置" Margin="0,10,0,0" Checked="cbOverrideConfig_Checked" Unchecked="cbOverrideConfig_Unchecked"/>
|
||||
|
||||
<Grid Name="gridOverrideConfig" Margin="0,10,0,0" IsEnabled="False">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="帧率:" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Name="txtFrameRate" Text="30" Margin="0,0,5,5"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Text="fps" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="动画时长:" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Name="txtDuration" Text="10.0" Margin="0,0,5,5"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="秒" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="检测间隙:" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Name="txtDetectionGap" Text="0.05" Margin="0,0,5,5"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="2" Text="米" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
|
||||
<Button Content="确定" Name="btnOK" Click="btnOK_Click"
|
||||
Style="{StaticResource ActionButtonStyle}" Width="100" Margin="0,0,10,0"/>
|
||||
<Button Content="取消" Name="btnCancel" Click="btnCancel_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}" Width="100"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
297
src/UI/WPF/Views/PathConfigDialog.xaml.cs
Normal file
297
src/UI/WPF/Views/PathConfigDialog.xaml.cs
Normal file
@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisworksTransport.Core.Config;
|
||||
using NavisworksTransport.Core.Models;
|
||||
using NavisworksTransport.Utils;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// 路径配置对话框
|
||||
/// </summary>
|
||||
public partial class PathConfigDialog : Window
|
||||
{
|
||||
private List<ModelItem> _detectionItems;
|
||||
private ModelItem _selectedObject;
|
||||
|
||||
public PathConfigDialog(CollisionDetectionConfig existingConfig = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_detectionItems = new List<ModelItem>();
|
||||
_selectedObject = null;
|
||||
|
||||
// 从系统配置读取虚拟车辆默认值
|
||||
var systemConfig = ConfigManager.Instance.Current;
|
||||
txtVehicleLength.Text = systemConfig.PathEditing.VehicleLengthMeters.ToString("F2");
|
||||
txtVehicleWidth.Text = systemConfig.PathEditing.VehicleWidthMeters.ToString("F2");
|
||||
txtVehicleHeight.Text = systemConfig.PathEditing.VehicleHeightMeters.ToString("F2");
|
||||
|
||||
// 设置初始状态(在 InitializeComponent 之后,避免触发 Checked 事件)
|
||||
rbVirtualVehicle.IsChecked = true;
|
||||
|
||||
// 如果有现有配置,加载它(会覆盖上面的默认值)
|
||||
if (existingConfig != null)
|
||||
{
|
||||
LoadExistingConfig(existingConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置
|
||||
/// </summary>
|
||||
public PathConfigResult GetResult()
|
||||
{
|
||||
var result = new PathConfigResult
|
||||
{
|
||||
IsVirtualVehicle = rbVirtualVehicle.IsChecked == true,
|
||||
VirtualVehicleLength = double.Parse(txtVehicleLength.Text),
|
||||
VirtualVehicleWidth = double.Parse(txtVehicleWidth.Text),
|
||||
VirtualVehicleHeight = double.Parse(txtVehicleHeight.Text),
|
||||
VehicleObjectId = _selectedObject?.ToString(),
|
||||
VehicleObjectName = _selectedObject?.DisplayName,
|
||||
DetectionItems = _detectionItems.Select(i => i.ToString()).ToList(),
|
||||
DetectAllObjects = rbDetectAll.IsChecked == true,
|
||||
UseOverrideConfig = cbOverrideConfig.IsChecked == true
|
||||
};
|
||||
|
||||
if (result.UseOverrideConfig)
|
||||
{
|
||||
result.ConfigOverride = new CollisionDetectionConfig
|
||||
{
|
||||
FrameRate = int.Parse(txtFrameRate.Text),
|
||||
DurationSeconds = double.Parse(txtDuration.Text),
|
||||
DetectionGapMeters = double.Parse(txtDetectionGap.Text)
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载现有配置
|
||||
/// </summary>
|
||||
private void LoadExistingConfig(CollisionDetectionConfig config)
|
||||
{
|
||||
if (config == null) return;
|
||||
|
||||
txtVehicleLength.Text = config.VirtualVehicleLength.ToString("F2");
|
||||
txtVehicleWidth.Text = config.VirtualVehicleWidth.ToString("F2");
|
||||
txtVehicleHeight.Text = config.VirtualVehicleHeight.ToString("F2");
|
||||
txtFrameRate.Text = config.FrameRate.ToString();
|
||||
txtDuration.Text = config.DurationSeconds.ToString("F2");
|
||||
txtDetectionGap.Text = config.DetectionGapMeters.ToString("F2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 虚拟车辆选中
|
||||
/// </summary>
|
||||
private void rbVirtualVehicle_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
borderVirtualVehicle.Visibility = Visibility.Visible;
|
||||
borderRealObject.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 真实物物体选中
|
||||
/// </summary>
|
||||
private void rbRealObject_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
borderVirtualVehicle.Visibility = Visibility.Collapsed;
|
||||
borderRealObject.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从视图选择物体
|
||||
/// </summary>
|
||||
private void btnSelectFromView_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取当前选中的物体
|
||||
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
|
||||
if (doc == null)
|
||||
{
|
||||
MessageBox.Show("没有打开的文档", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var selection = doc.CurrentSelection;
|
||||
if (selection == null || selection.IsEmpty)
|
||||
{
|
||||
MessageBox.Show("请先在视图或选择树中选择物体", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedItems = selection.SelectedItems;
|
||||
if (selectedItems.Count > 1)
|
||||
{
|
||||
MessageBox.Show("请只选择一个物体", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedObject = selectedItems.First;
|
||||
txtSelectedObject.Text = _selectedObject.DisplayName;
|
||||
txtObjectId.Text = _selectedObject.ToString();
|
||||
|
||||
LogManager.Info($"[路径配置] 已选择物体: {_selectedObject.DisplayName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"选择物体失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
LogManager.Error($"[路径配置] 选择物体失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选择检测项
|
||||
/// </summary>
|
||||
private void btnSelectDetectionItems_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
|
||||
if (doc == null)
|
||||
{
|
||||
MessageBox.Show("没有打开的文档", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var selection = doc.CurrentSelection;
|
||||
if (selection == null || selection.IsEmpty)
|
||||
{
|
||||
MessageBox.Show("请先在视图或选择树中选择检测项", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
_detectionItems.Clear();
|
||||
var selectedItems = selection.SelectedItems;
|
||||
foreach (var item in selectedItems)
|
||||
{
|
||||
_detectionItems.Add(item);
|
||||
}
|
||||
|
||||
txtDetectionItemCount.Text = _detectionItems.Count.ToString();
|
||||
|
||||
LogManager.Info($"[路径配置] 已选择 {_detectionItems.Count} 个检测项");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"选择检测项失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
LogManager.Error($"[路径配置] 选择检测项失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用配置覆盖
|
||||
/// </summary>
|
||||
private void cbOverrideConfig_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
gridOverrideConfig.IsEnabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用配置覆盖
|
||||
/// </summary>
|
||||
private void cbOverrideConfig_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
gridOverrideConfig.IsEnabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定
|
||||
/// </summary>
|
||||
private void btnOK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证虚拟车辆参数
|
||||
if (rbVirtualVehicle.IsChecked == true)
|
||||
{
|
||||
if (!double.TryParse(txtVehicleLength.Text, out var length) || length <= 0)
|
||||
{
|
||||
MessageBox.Show("请输入有效的车辆长度", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(txtVehicleWidth.Text, out var width) || width <= 0)
|
||||
{
|
||||
MessageBox.Show("请输入有效的车辆宽度", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(txtVehicleHeight.Text, out var height) || height <= 0)
|
||||
{
|
||||
MessageBox.Show("请输入有效的车辆高度", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证真实物体选择
|
||||
if (rbRealObject.IsChecked == true && _selectedObject == null)
|
||||
{
|
||||
MessageBox.Show("请选择一个真实物体", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证配置覆盖参数
|
||||
if (cbOverrideConfig.IsChecked == true)
|
||||
{
|
||||
if (!int.TryParse(txtFrameRate.Text, out var frameRate) || frameRate <= 0)
|
||||
{
|
||||
MessageBox.Show("请输入有效的帧率", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(txtDuration.Text, out var duration) || duration <= 0)
|
||||
{
|
||||
MessageBox.Show("请输入有效的动画时长", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(txtDetectionGap.Text, out var gap) || gap <= 0)
|
||||
{
|
||||
MessageBox.Show("请输入有效的检测间隙", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"验证失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消
|
||||
/// </summary>
|
||||
private void btnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 路径配置结果
|
||||
/// </summary>
|
||||
public class PathConfigResult
|
||||
{
|
||||
public bool IsVirtualVehicle { get; set; }
|
||||
public double VirtualVehicleLength { get; set; }
|
||||
public double VirtualVehicleWidth { get; set; }
|
||||
public double VirtualVehicleHeight { get; set; }
|
||||
public string VehicleObjectId { get; set; }
|
||||
public string VehicleObjectName { get; set; }
|
||||
public List<string> DetectionItems { get; set; }
|
||||
public bool DetectAllObjects { get; set; }
|
||||
public bool UseOverrideConfig { get; set; }
|
||||
public CollisionDetectionConfig ConfigOverride { get; set; }
|
||||
}
|
||||
}
|
||||
82
src/UI/WPF/Views/PathSelectionDialog.xaml
Normal file
82
src/UI/WPF/Views/PathSelectionDialog.xaml
Normal file
@ -0,0 +1,82 @@
|
||||
<!--
|
||||
路径选择对话框
|
||||
|
||||
功能说明:
|
||||
1. 从数据库加载所有路径
|
||||
2. 支持多选
|
||||
3. 显示路径基本信息(名称、类型、长度)
|
||||
-->
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.PathSelectionDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
Title="选择路径"
|
||||
Height="500"
|
||||
Width="800"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<Border Grid.Row="0" BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Padding="12" Margin="0,0,0,10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" Content="搜索:" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="1" Name="txtSearch" Margin="5,0,5,0" TextChanged="txtSearch_TextChanged"/>
|
||||
<Button Grid.Column="2" Content="全选" Name="btnSelectAll" Click="btnSelectAll_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}" Width="80" Margin="5,0,0,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 路径列表 -->
|
||||
<DataGrid Grid.Row="1"
|
||||
Name="dgPaths"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
SelectionMode="Extended"
|
||||
HeadersVisibility="Column"
|
||||
GridLinesVisibility="Horizontal"
|
||||
Margin="0,0,0,10">
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn Header="选择" Binding="{Binding IsSelected}" Width="50"/>
|
||||
<DataGridTextColumn Header="路径名称" Binding="{Binding Name}" Width="*"/>
|
||||
<DataGridTextColumn Header="类型" Binding="{Binding PathTypeDisplay}" Width="100"/>
|
||||
<DataGridTextColumn Header="长度" Binding="{Binding TotalLengthDisplay}" Width="100"/>
|
||||
<DataGridTextColumn Header="点数" Binding="{Binding PointCount}" Width="80"/>
|
||||
<DataGridTextColumn Header="创建时间" Binding="{Binding CreatedTimeDisplay}" Width="150"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
|
||||
<TextBlock Name="txtSelectedCount" VerticalAlignment="Center" Margin="0,0,20,0"
|
||||
Text="已选择: 0 条路径"/>
|
||||
<Button Content="确定" Name="btnOK" Click="btnOK_Click"
|
||||
Style="{StaticResource ActionButtonStyle}" Width="100" Margin="0,0,10,0"/>
|
||||
<Button Content="取消" Name="btnCancel" Click="btnCancel_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}" Width="100"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
175
src/UI/WPF/Views/PathSelectionDialog.xaml.cs
Normal file
175
src/UI/WPF/Views/PathSelectionDialog.xaml.cs
Normal file
@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using NavisworksTransport.Core;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// 路径选择对话框
|
||||
/// </summary>
|
||||
public partial class PathSelectionDialog : Window
|
||||
{
|
||||
private ObservableCollection<PathRouteDisplayItem> _allPaths;
|
||||
private ObservableCollection<PathRouteDisplayItem> _filteredPaths;
|
||||
|
||||
public PathSelectionDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_allPaths = new ObservableCollection<PathRouteDisplayItem>();
|
||||
_filteredPaths = new ObservableCollection<PathRouteDisplayItem>();
|
||||
dgPaths.ItemsSource = _filteredPaths;
|
||||
|
||||
LoadPaths();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载路径列表
|
||||
/// </summary>
|
||||
private void LoadPaths()
|
||||
{
|
||||
try
|
||||
{
|
||||
var database = PathPlanningManager.Instance?.GetPathDatabase();
|
||||
if (database == null)
|
||||
{
|
||||
MessageBox.Show("数据库未初始化", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var routes = database.GetAllPathRoutes();
|
||||
|
||||
_allPaths.Clear();
|
||||
foreach (var route in routes)
|
||||
{
|
||||
_allPaths.Add(new PathRouteDisplayItem(route));
|
||||
}
|
||||
|
||||
_filteredPaths.Clear();
|
||||
foreach (var item in _allPaths)
|
||||
{
|
||||
_filteredPaths.Add(item);
|
||||
}
|
||||
|
||||
UpdateSelectedCount();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"加载路径失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取选中的路径
|
||||
/// </summary>
|
||||
public List<PathRoute> GetSelectedPaths()
|
||||
{
|
||||
return _allPaths.Where(p => p.IsSelected).Select(p => p.Route).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索框文本变化
|
||||
/// </summary>
|
||||
private void txtSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
var searchText = txtSearch.Text.ToLower();
|
||||
|
||||
_filteredPaths.Clear();
|
||||
foreach (var item in _allPaths)
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchText) ||
|
||||
item.Name.ToLower().Contains(searchText) ||
|
||||
item.PathTypeDisplay.ToLower().Contains(searchText))
|
||||
{
|
||||
_filteredPaths.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全选
|
||||
/// </summary>
|
||||
private void btnSelectAll_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var allSelected = _filteredPaths.All(p => p.IsSelected);
|
||||
|
||||
foreach (var item in _filteredPaths)
|
||||
{
|
||||
item.IsSelected = !allSelected;
|
||||
}
|
||||
|
||||
UpdateSelectedCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新选中数量显示
|
||||
/// </summary>
|
||||
private void UpdateSelectedCount()
|
||||
{
|
||||
txtSelectedCount.Text = $"已选择: {_allPaths.Count(p => p.IsSelected)} 条路径";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定
|
||||
/// </summary>
|
||||
private void btnOK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectedPaths = GetSelectedPaths();
|
||||
if (selectedPaths.Count == 0)
|
||||
{
|
||||
MessageBox.Show("请至少选择一条路径", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消
|
||||
/// </summary>
|
||||
private void btnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 路径显示项
|
||||
/// </summary>
|
||||
public class PathRouteDisplayItem
|
||||
{
|
||||
public PathRoute Route { get; }
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public string Name => Route.Name;
|
||||
public string PathTypeDisplay => GetPathTypeDisplay(Route.PathType);
|
||||
public string TotalLengthDisplay => $"{Route.TotalLength:F2} 米";
|
||||
public int PointCount => Route.Points?.Count ?? 0;
|
||||
public string CreatedTimeDisplay => Route.CreatedTime.ToString("yyyy-MM-dd HH:mm");
|
||||
|
||||
public PathRouteDisplayItem(PathRoute route)
|
||||
{
|
||||
Route = route;
|
||||
IsSelected = false;
|
||||
}
|
||||
|
||||
private string GetPathTypeDisplay(PathType pathType)
|
||||
{
|
||||
switch (pathType)
|
||||
{
|
||||
case PathType.Ground:
|
||||
return "地面";
|
||||
case PathType.Aerial:
|
||||
return "空中";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user