2556 lines
92 KiB
C#
2556 lines
92 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.IO;
|
||
using Autodesk.Navisworks.Api;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// 路径规划管理器
|
||
/// 负责路径规划的核心业务逻辑
|
||
/// </summary>
|
||
public class PathPlanningManager
|
||
{
|
||
private CategoryAttributeManager _categoryManager;
|
||
private VisibilityManager _visibilityManager;
|
||
private CoordinateConverter _coordinateConverter;
|
||
private NavigationMapWindow _mapWindow;
|
||
private List<ModelItem> _selectedChannels;
|
||
private List<PathRoute> _routes;
|
||
private PathRoute _currentRoute;
|
||
private ChannelBounds _combinedChannelBounds;
|
||
|
||
// 新增:3D交互模式相关
|
||
private bool _isPathEditMode = false;
|
||
private PathPointType _currentPointType = PathPointType.WayPoint;
|
||
|
||
// 静态标志,用于跨实例跟踪3D编辑模式状态
|
||
private static bool _globalIsPathEditMode = false;
|
||
|
||
// 日志文件路径
|
||
private static string _logFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "NavisworksTransport_Debug.log");
|
||
|
||
/// <summary>
|
||
/// 是否处于路径编辑模式
|
||
/// </summary>
|
||
public bool IsPathEditMode
|
||
{
|
||
get { return _isPathEditMode || _globalIsPathEditMode; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 全局3D编辑模式状态(静态属性)
|
||
/// </summary>
|
||
public static bool GlobalIsPathEditMode
|
||
{
|
||
get { return _globalIsPathEditMode; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前设置的路径点类型
|
||
/// </summary>
|
||
public PathPointType CurrentPointType
|
||
{
|
||
get { return _currentPointType; }
|
||
set { _currentPointType = value; }
|
||
}
|
||
|
||
// 新增事件
|
||
public event EventHandler<bool> PathEditModeChanged;
|
||
public event EventHandler<PathPoint> PathPointAddedIn3D;
|
||
|
||
/// <summary>
|
||
/// 当前选中的通道集合
|
||
/// </summary>
|
||
public List<ModelItem> SelectedChannels
|
||
{
|
||
get { return _selectedChannels; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 所有路径集合
|
||
/// </summary>
|
||
public List<PathRoute> Routes
|
||
{
|
||
get { return _routes; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前活动路径
|
||
/// </summary>
|
||
public PathRoute CurrentRoute
|
||
{
|
||
get { return _currentRoute; }
|
||
set
|
||
{
|
||
_currentRoute = value;
|
||
if (_mapWindow != null)
|
||
{
|
||
_mapWindow.CurrentRoute = _currentRoute;
|
||
}
|
||
CurrentRouteChanged?.Invoke(this, _currentRoute);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组合通道边界
|
||
/// </summary>
|
||
public ChannelBounds CombinedChannelBounds
|
||
{
|
||
get { return _combinedChannelBounds; }
|
||
}
|
||
|
||
// 事件定义
|
||
public event EventHandler<List<ModelItem>> ChannelsSelected;
|
||
public event EventHandler<PathRoute> CurrentRouteChanged;
|
||
public event EventHandler<PathRoute> RouteGenerated;
|
||
public event EventHandler<string> StatusChanged;
|
||
public event EventHandler<string> ErrorOccurred;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="categoryManager">类别属性管理器</param>
|
||
/// <param name="visibilityManager">可见性管理器</param>
|
||
public PathPlanningManager(CategoryAttributeManager categoryManager, VisibilityManager visibilityManager)
|
||
{
|
||
_categoryManager = categoryManager ?? throw new ArgumentNullException(nameof(categoryManager));
|
||
_visibilityManager = visibilityManager ?? throw new ArgumentNullException(nameof(visibilityManager));
|
||
|
||
_selectedChannels = new List<ModelItem>();
|
||
_routes = new List<PathRoute>();
|
||
_currentRoute = new PathRoute("默认路径");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 无参构造函数(为向后兼容而保留)
|
||
/// </summary>
|
||
public PathPlanningManager()
|
||
{
|
||
_categoryManager = new CategoryAttributeManager();
|
||
_visibilityManager = new VisibilityManager();
|
||
|
||
_selectedChannels = new List<ModelItem>();
|
||
_routes = new List<PathRoute>();
|
||
_currentRoute = new PathRoute("默认路径");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选择通道模型
|
||
/// </summary>
|
||
/// <param name="useCurrentSelection">是否使用当前选择的模型</param>
|
||
/// <returns>选择的通道数量</returns>
|
||
public int SelectChannels(bool useCurrentSelection = true)
|
||
{
|
||
try
|
||
{
|
||
_selectedChannels.Clear();
|
||
|
||
if (useCurrentSelection)
|
||
{
|
||
// 使用当前选择的模型
|
||
var currentSelection = Application.ActiveDocument.CurrentSelection.SelectedItems;
|
||
if (currentSelection.Any())
|
||
{
|
||
_selectedChannels.AddRange(currentSelection);
|
||
OnStatusChanged($"已选择 {_selectedChannels.Count} 个模型作为通道");
|
||
}
|
||
else
|
||
{
|
||
OnStatusChanged("未选择任何模型,请先选择通道模型");
|
||
return 0;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 通过类别属性筛选通道
|
||
var channelItems = FilterChannelsByCategory();
|
||
_selectedChannels.AddRange(channelItems);
|
||
OnStatusChanged($"通过类别筛选到 {_selectedChannels.Count} 个通道");
|
||
}
|
||
|
||
if (_selectedChannels.Any())
|
||
{
|
||
// 计算组合边界
|
||
CalculateCombinedBounds();
|
||
|
||
// 触发事件
|
||
ChannelsSelected?.Invoke(this, _selectedChannels);
|
||
}
|
||
|
||
return _selectedChannels.Count;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"选择通道时发生错误: {ex.Message}");
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过类别筛选通道模型
|
||
/// </summary>
|
||
/// <returns>通道模型集合</returns>
|
||
private List<ModelItem> FilterChannelsByCategory()
|
||
{
|
||
var channelItems = new List<ModelItem>();
|
||
|
||
try
|
||
{
|
||
// 获取所有模型项
|
||
var allItems = new List<ModelItem>();
|
||
foreach (ModelItem rootItem in Application.ActiveDocument.Models.RootItems)
|
||
{
|
||
CollectAllItems(rootItem, allItems);
|
||
}
|
||
|
||
foreach (var item in allItems)
|
||
{
|
||
// 检查是否有物流类别属性
|
||
if (CategoryAttributeManager.HasLogisticsAttributes(item))
|
||
{
|
||
// 通过FilterByLogisticsType方法检查是否为通道类型
|
||
var singleItemCollection = new ModelItemCollection();
|
||
singleItemCollection.Add(item);
|
||
var filteredItems = CategoryAttributeManager.FilterByLogisticsType(singleItemCollection, CategoryAttributeManager.LogisticsElementType.通道);
|
||
if (filteredItems.Count > 0)
|
||
{
|
||
channelItems.Add(item);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"筛选通道类别时发生错误: {ex.Message}");
|
||
}
|
||
|
||
return channelItems;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算组合通道边界
|
||
/// </summary>
|
||
private void CalculateCombinedBounds()
|
||
{
|
||
if (!_selectedChannels.Any())
|
||
{
|
||
_combinedChannelBounds = null;
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 获取单位转换系数
|
||
var conversionFactor = GetUnitsToMetersConversionFactor();
|
||
var units = Application.ActiveDocument.Units;
|
||
OnStatusChanged($"检测到文档单位: {units},转换系数: {conversionFactor}");
|
||
|
||
// 初始化边界值
|
||
var allBounds = new List<BoundingBox3D>();
|
||
|
||
foreach (var channel in _selectedChannels)
|
||
{
|
||
try
|
||
{
|
||
var originalBoundingBox = channel.BoundingBox();
|
||
if (originalBoundingBox != null)
|
||
{
|
||
// 转换包围盒到米单位
|
||
var boundingBoxInMeters = ConvertBoundingBoxToMeters(originalBoundingBox, conversionFactor);
|
||
allBounds.Add(boundingBoxInMeters);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略无法获取边界的模型
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (allBounds.Any())
|
||
{
|
||
// 计算组合边界
|
||
var minX = allBounds.Min(b => b.Min.X);
|
||
var minY = allBounds.Min(b => b.Min.Y);
|
||
var minZ = allBounds.Min(b => b.Min.Z);
|
||
var maxX = allBounds.Max(b => b.Max.X);
|
||
var maxY = allBounds.Max(b => b.Max.Y);
|
||
var maxZ = allBounds.Max(b => b.Max.Z);
|
||
|
||
// 检查边界尺寸是否合理
|
||
var width = maxX - minX;
|
||
var length = maxY - minY;
|
||
var height = maxZ - minZ;
|
||
var maxDimension = Math.Max(width, Math.Max(length, height));
|
||
|
||
if (maxDimension > 1000) // 超过1公里,可能是坐标系统问题
|
||
{
|
||
OnStatusChanged($"警告:通道边界过大({maxDimension:F2}m),将限制到合理范围");
|
||
|
||
// 计算中心点
|
||
var centerX = (minX + maxX) / 2;
|
||
var centerY = (minY + maxY) / 2;
|
||
var centerZ = (minZ + maxZ) / 2;
|
||
|
||
// 限制到合理范围(100米)
|
||
var limitedSize = 100;
|
||
var halfSize = limitedSize / 2;
|
||
|
||
minX = centerX - halfSize;
|
||
maxX = centerX + halfSize;
|
||
minY = centerY - halfSize;
|
||
maxY = centerY + halfSize;
|
||
minZ = centerZ - halfSize;
|
||
maxZ = centerZ + halfSize;
|
||
}
|
||
|
||
var combinedBoundingBox = new BoundingBox3D(
|
||
new Point3D(minX, minY, minZ),
|
||
new Point3D(maxX, maxY, maxZ)
|
||
);
|
||
|
||
_combinedChannelBounds = new ChannelBounds(combinedBoundingBox);
|
||
|
||
OnStatusChanged($"已计算通道边界: {_combinedChannelBounds.MinPoint.X:F2},{_combinedChannelBounds.MinPoint.Y:F2} - {_combinedChannelBounds.MaxPoint.X:F2},{_combinedChannelBounds.MaxPoint.Y:F2}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"计算通道边界时发生错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示导航地图窗口
|
||
/// </summary>
|
||
/// <param name="mapWidth">地图宽度</param>
|
||
/// <param name="mapHeight">地图高度</param>
|
||
/// <returns>是否成功显示</returns>
|
||
public bool ShowNavigationMap(double mapWidth = 800, double mapHeight = 600)
|
||
{
|
||
try
|
||
{
|
||
if (_mapWindow != null && !_mapWindow.IsDisposed)
|
||
{
|
||
_mapWindow.BringToFront();
|
||
return true;
|
||
}
|
||
|
||
// 确保有选中的通道
|
||
if (_selectedChannels.Count == 0)
|
||
{
|
||
var selectionResult = ShowChannelSelectionDialog();
|
||
if (!selectionResult.Success || selectionResult.SelectedChannels.Count == 0)
|
||
{
|
||
OnStatusChanged("未选择通道,无法显示导航地图");
|
||
return false;
|
||
}
|
||
|
||
// 更新选中的通道
|
||
_selectedChannels.Clear();
|
||
_selectedChannels.AddRange(selectionResult.SelectedChannels);
|
||
CalculateCombinedBounds();
|
||
}
|
||
|
||
// 创建坐标转换器
|
||
if (_coordinateConverter == null)
|
||
{
|
||
_coordinateConverter = new CoordinateConverter(_combinedChannelBounds, mapWidth, mapHeight);
|
||
}
|
||
|
||
// 创建并显示导航地图窗口
|
||
_mapWindow = new NavigationMapWindow(_coordinateConverter, _selectedChannels);
|
||
_mapWindow.CurrentRoute = _currentRoute;
|
||
|
||
SetupMapWindowEvents();
|
||
|
||
_mapWindow.Show();
|
||
OnStatusChanged("导航地图已打开");
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"显示导航地图失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示路径规划界面(MainPlugin兼容方法)
|
||
/// </summary>
|
||
/// <returns>是否成功显示</returns>
|
||
public bool ShowPathPlanningInterface()
|
||
{
|
||
return ShowNavigationMap();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加路径到管理器(MainPlugin兼容方法)
|
||
/// </summary>
|
||
/// <param name="route">要添加的路径</param>
|
||
/// <returns>是否成功添加</returns>
|
||
public bool AddRoute(PathRoute route)
|
||
{
|
||
try
|
||
{
|
||
if (route == null)
|
||
{
|
||
OnErrorOccurred("无法添加空路径");
|
||
return false;
|
||
}
|
||
|
||
// 检查是否已存在同名路径
|
||
var existingRoute = _routes.FirstOrDefault(r => r.Name == route.Name);
|
||
if (existingRoute != null)
|
||
{
|
||
// 如果存在同名路径,生成唯一名称
|
||
int counter = 1;
|
||
string originalName = route.Name;
|
||
while (_routes.Any(r => r.Name == route.Name))
|
||
{
|
||
route.Name = $"{originalName}_{counter}";
|
||
counter++;
|
||
}
|
||
}
|
||
|
||
_routes.Add(route);
|
||
OnStatusChanged($"已添加路径: {route.Name}");
|
||
|
||
// 如果当前没有活动路径,设置此路径为活动路径
|
||
if (_currentRoute == null || _currentRoute.Points.Count == 0)
|
||
{
|
||
CurrentRoute = route;
|
||
}
|
||
|
||
// 触发路径生成事件
|
||
RouteGenerated?.Invoke(this, route);
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"添加路径失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置地图窗口事件处理器
|
||
/// </summary>
|
||
private void SetupMapWindowEvents()
|
||
{
|
||
if (_mapWindow != null)
|
||
{
|
||
_mapWindow.PathGenerated += MapWindow_PathGenerated;
|
||
_mapWindow.PointSelected += MapWindow_PointSelected;
|
||
_mapWindow.PointAdded += MapWindow_PointAdded;
|
||
_mapWindow.PointRemoved += MapWindow_PointRemoved;
|
||
_mapWindow.FormClosed += MapWindow_FormClosed;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建新路径
|
||
/// </summary>
|
||
/// <param name="routeName">路径名称</param>
|
||
/// <returns>新创建的路径</returns>
|
||
public PathRoute CreateNewRoute(string routeName = null)
|
||
{
|
||
if (string.IsNullOrEmpty(routeName))
|
||
{
|
||
routeName = $"路径_{_routes.Count + 1}";
|
||
}
|
||
|
||
var newRoute = new PathRoute(routeName);
|
||
_routes.Add(newRoute);
|
||
CurrentRoute = newRoute;
|
||
|
||
OnStatusChanged($"已创建新路径: {routeName}");
|
||
return newRoute;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除路径
|
||
/// </summary>
|
||
/// <param name="route">要删除的路径</param>
|
||
/// <returns>是否成功删除</returns>
|
||
public bool DeleteRoute(PathRoute route)
|
||
{
|
||
if (route == null) return false;
|
||
|
||
try
|
||
{
|
||
bool removed = _routes.Remove(route);
|
||
if (removed)
|
||
{
|
||
if (_currentRoute == route)
|
||
{
|
||
_currentRoute = _routes.FirstOrDefault() ?? new PathRoute("默认路径");
|
||
CurrentRouteChanged?.Invoke(this, _currentRoute);
|
||
}
|
||
OnStatusChanged($"已删除路径: {route.Name}");
|
||
}
|
||
return removed;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"删除路径时发生错误: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成路径
|
||
/// </summary>
|
||
/// <param name="route">要生成的路径,为null时使用当前路径</param>
|
||
/// <returns>是否成功生成</returns>
|
||
public bool GeneratePath(PathRoute route = null)
|
||
{
|
||
route = route ?? _currentRoute;
|
||
if (route == null) return false;
|
||
|
||
try
|
||
{
|
||
// 验证路径有效性
|
||
if (!route.IsValid())
|
||
{
|
||
OnErrorOccurred("路径无效:必须包含至少一个起点和一个终点");
|
||
return false;
|
||
}
|
||
|
||
// 更新路径关联的通道ID
|
||
route.AssociatedChannelIds.Clear();
|
||
foreach (var channel in _selectedChannels)
|
||
{
|
||
// 这里可以添加更复杂的逻辑来获取模型ID
|
||
route.AssociatedChannelIds.Add(channel.InstanceGuid.ToString());
|
||
}
|
||
|
||
// 计算预估时间(简单实现)
|
||
CalculateEstimatedTime(route);
|
||
|
||
OnStatusChanged($"路径生成成功: {route.Name}, 长度: {route.TotalLength:F2}米, 预估时间: {route.EstimatedTime:F1}秒");
|
||
RouteGenerated?.Invoke(this, route);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"生成路径时发生错误: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算预估时间
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
private void CalculateEstimatedTime(PathRoute route)
|
||
{
|
||
// 简单的时间估算:假设平均速度1米/秒
|
||
const double averageSpeed = 1.0; // 米/秒
|
||
route.EstimatedTime = route.TotalLength / averageSpeed;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 隐藏非通道元素
|
||
/// </summary>
|
||
/// <returns>是否成功</returns>
|
||
public bool HideNonChannelElements()
|
||
{
|
||
try
|
||
{
|
||
if (!_selectedChannels.Any())
|
||
{
|
||
OnErrorOccurred("请先选择通道模型");
|
||
return false;
|
||
}
|
||
|
||
// 获取所有非通道元素
|
||
var allItems = new List<ModelItem>();
|
||
foreach (ModelItem rootItem in Application.ActiveDocument.Models.RootItems)
|
||
{
|
||
CollectAllItems(rootItem, allItems);
|
||
}
|
||
var nonChannelItems = allItems.Except(_selectedChannels).ToList();
|
||
|
||
// 使用可见性管理器隐藏非通道元素
|
||
var result = _visibilityManager.HideNonLogisticsItemsInstance();
|
||
|
||
if (result.Success)
|
||
{
|
||
OnStatusChanged($"已隐藏 {nonChannelItems.Count} 个非通道元素");
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
OnErrorOccurred($"隐藏非通道元素失败: {result.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"隐藏非通道元素时发生错误: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示所有元素
|
||
/// </summary>
|
||
/// <returns>是否成功</returns>
|
||
public bool ShowAllElements()
|
||
{
|
||
try
|
||
{
|
||
var result = _visibilityManager.ShowAllItemsInstance();
|
||
|
||
if (result.Success)
|
||
{
|
||
OnStatusChanged("已显示所有元素");
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
OnErrorOccurred($"显示所有元素失败: {result.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"显示所有元素时发生错误: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径统计信息
|
||
/// </summary>
|
||
/// <returns>统计信息字符串</returns>
|
||
public string GetStatistics()
|
||
{
|
||
var stats = $"通道数量: {_selectedChannels.Count}\n";
|
||
stats += $"路径数量: {_routes.Count}\n";
|
||
|
||
if (_currentRoute != null)
|
||
{
|
||
stats += $"当前路径: {_currentRoute.Name}\n";
|
||
stats += $"路径点数: {_currentRoute.Points.Count}\n";
|
||
stats += $"路径长度: {_currentRoute.TotalLength:F2}米\n";
|
||
stats += $"预估时间: {_currentRoute.EstimatedTime:F1}秒\n";
|
||
}
|
||
|
||
if (_combinedChannelBounds != null)
|
||
{
|
||
double scaleX = 0, scaleY = 0;
|
||
if (_coordinateConverter != null)
|
||
{
|
||
_coordinateConverter.GetMapScale(out scaleX, out scaleY);
|
||
}
|
||
stats += $"通道范围: {_combinedChannelBounds.MinPoint.X:F2},{_combinedChannelBounds.MinPoint.Y:F2} - {_combinedChannelBounds.MaxPoint.X:F2},{_combinedChannelBounds.MaxPoint.Y:F2}\n";
|
||
stats += $"地图缩放: {scaleX:F4}, {scaleY:F4}";
|
||
}
|
||
|
||
return stats;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理资源
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
// 停止3D点击监听
|
||
Stop3DClickListener();
|
||
|
||
if (_mapWindow != null && !_mapWindow.IsDisposed)
|
||
{
|
||
_mapWindow.Close();
|
||
_mapWindow.Dispose();
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略清理错误
|
||
}
|
||
}
|
||
|
||
#region 事件处理器
|
||
|
||
private void MapWindow_PathGenerated(object sender, PathRoute route)
|
||
{
|
||
GeneratePath(route);
|
||
}
|
||
|
||
private void MapWindow_PointSelected(object sender, PathPoint point)
|
||
{
|
||
OnStatusChanged($"已选中路径点: {point.Name} ({point.Type})");
|
||
}
|
||
|
||
private void MapWindow_PointAdded(object sender, PathPoint point)
|
||
{
|
||
OnStatusChanged($"已添加路径点: {point.Name} ({point.Type})");
|
||
}
|
||
|
||
private void MapWindow_PointRemoved(object sender, PathPoint point)
|
||
{
|
||
OnStatusChanged($"已移除路径点: {point.Name} ({point.Type})");
|
||
}
|
||
|
||
private void MapWindow_FormClosed(object sender, System.Windows.Forms.FormClosedEventArgs e)
|
||
{
|
||
OnStatusChanged("导航地图窗口已关闭");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 事件触发方法
|
||
|
||
private void OnStatusChanged(string status)
|
||
{
|
||
StatusChanged?.Invoke(this, status);
|
||
}
|
||
|
||
private void OnErrorOccurred(string error)
|
||
{
|
||
ErrorOccurred?.Invoke(this, error);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入调试日志
|
||
/// </summary>
|
||
/// <param name="message">日志消息</param>
|
||
private static void WriteLog(string message)
|
||
{
|
||
try
|
||
{
|
||
string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}";
|
||
File.AppendAllText(_logFilePath, logEntry + Environment.NewLine);
|
||
}
|
||
catch
|
||
{
|
||
// 忽略日志写入错误
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 获取所有通道选择结果
|
||
/// </summary>
|
||
/// <returns>通道选择结果</returns>
|
||
public ChannelSelectionResult GetAllChannelSelectionResults()
|
||
{
|
||
var result = new ChannelSelectionResult();
|
||
|
||
try
|
||
{
|
||
// 获取所有模型项
|
||
var allItems = new List<ModelItem>();
|
||
foreach (ModelItem rootItem in Application.ActiveDocument.Models.RootItems)
|
||
{
|
||
CollectAllItems(rootItem, allItems);
|
||
}
|
||
var filteredItems = allItems.Where(item => item.HasGeometry).ToArray();
|
||
|
||
result.TotalModelItems = filteredItems.Length;
|
||
|
||
// 自动检测通道
|
||
result.AutoDetectedChannels = AutoDetectChannels(filteredItems);
|
||
|
||
// 筛选已标记的物流元素
|
||
result.LogisticsMarkedItems = FilterLogisticsMarkedItems(filteredItems);
|
||
|
||
// 筛选可通行区域
|
||
result.TraversableAreas = FilterTraversableAreas(result.LogisticsMarkedItems);
|
||
|
||
// 筛选通道类型
|
||
result.ChannelItems = FilterChannelItems(result.LogisticsMarkedItems);
|
||
|
||
result.Success = true;
|
||
result.Message = $"成功分析 {result.TotalModelItems} 个模型项";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result.Success = false;
|
||
result.Message = $"通道分析失败: {ex.Message}";
|
||
System.Diagnostics.Debug.WriteLine($"获取通道选择结果失败: {ex.Message}");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动检测可能的通道
|
||
/// </summary>
|
||
/// <param name="items">模型项数组</param>
|
||
/// <returns>自动检测的通道集合</returns>
|
||
private ModelItemCollection AutoDetectChannels(ModelItem[] items)
|
||
{
|
||
var channels = new ModelItemCollection();
|
||
|
||
try
|
||
{
|
||
foreach (var item in items)
|
||
{
|
||
// 根据几何特征和命名规则自动检测通道
|
||
if (IsLikelyChannel(item))
|
||
{
|
||
channels.Add(item);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"自动检测通道失败: {ex.Message}");
|
||
}
|
||
|
||
return channels;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断模型项是否可能是通道
|
||
/// </summary>
|
||
/// <param name="item">模型项</param>
|
||
/// <returns>是否可能是通道</returns>
|
||
private bool IsLikelyChannel(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
// 检查显示名称中的关键词
|
||
var displayName = item.DisplayName?.ToLower() ?? "";
|
||
var channelKeywords = new[] { "通道", "corridor", "passage", "walkway", "path", "道路", "路径" };
|
||
|
||
if (channelKeywords.Any(keyword => displayName.Contains(keyword)))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// 检查几何特征(简化判断)
|
||
if (item.HasGeometry)
|
||
{
|
||
var boundingBox = item.BoundingBox();
|
||
if (boundingBox != null)
|
||
{
|
||
var width = boundingBox.Max.X - boundingBox.Min.X;
|
||
var length = boundingBox.Max.Y - boundingBox.Min.Y;
|
||
var height = boundingBox.Max.Z - boundingBox.Min.Z;
|
||
|
||
// 判断是否为长条形(可能是通道)
|
||
var lengthToWidthRatio = Math.Max(width, length) / Math.Min(width, length);
|
||
var isLongNarrow = lengthToWidthRatio > 3.0 && height > 2.0 && height < 5.0;
|
||
|
||
if (isLongNarrow)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"判断通道特征失败: {ex.Message}");
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 筛选已标记物流属性的模型项
|
||
/// </summary>
|
||
/// <param name="items">模型项数组</param>
|
||
/// <returns>已标记的物流模型项集合</returns>
|
||
private ModelItemCollection FilterLogisticsMarkedItems(ModelItem[] items)
|
||
{
|
||
var markedItems = new ModelItemCollection();
|
||
|
||
try
|
||
{
|
||
foreach (var item in items)
|
||
{
|
||
if (CategoryAttributeManager.HasLogisticsAttributes(item))
|
||
{
|
||
markedItems.Add(item);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"筛选已标记物流项失败: {ex.Message}");
|
||
}
|
||
|
||
return markedItems;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 筛选可通行区域
|
||
/// </summary>
|
||
/// <param name="items">模型项集合</param>
|
||
/// <returns>可通行区域集合</returns>
|
||
private ModelItemCollection FilterTraversableAreas(ModelItemCollection items)
|
||
{
|
||
var traversableItems = new ModelItemCollection();
|
||
|
||
try
|
||
{
|
||
traversableItems = CategoryAttributeManager.FilterTraversableItems(items);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"筛选可通行区域失败: {ex.Message}");
|
||
}
|
||
|
||
return traversableItems;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 筛选通道类型的模型项
|
||
/// </summary>
|
||
/// <param name="items">模型项集合</param>
|
||
/// <returns>通道类型模型项集合</returns>
|
||
private ModelItemCollection FilterChannelItems(ModelItemCollection items)
|
||
{
|
||
var channelItems = new ModelItemCollection();
|
||
|
||
try
|
||
{
|
||
channelItems = CategoryAttributeManager.FilterByLogisticsType(items, CategoryAttributeManager.LogisticsElementType.通道);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"筛选通道类型项失败: {ex.Message}");
|
||
}
|
||
|
||
return channelItems;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据车辆尺寸筛选适用通道
|
||
/// </summary>
|
||
/// <param name="vehicleSize">车辆尺寸</param>
|
||
/// <returns>适用的通道集合</returns>
|
||
public ModelItemCollection FilterChannelsByVehicleSize(string vehicleSize)
|
||
{
|
||
var applicableChannels = new ModelItemCollection();
|
||
|
||
try
|
||
{
|
||
// 获取所有已标记的物流项
|
||
var allItems = new List<ModelItem>();
|
||
foreach (ModelItem rootItem in Application.ActiveDocument.Models.RootItems)
|
||
{
|
||
CollectAllItems(rootItem, allItems);
|
||
}
|
||
var filteredItems = allItems.Where(item => item.HasGeometry && CategoryAttributeManager.HasLogisticsAttributes(item)).ToArray();
|
||
|
||
var logisticsItems = new ModelItemCollection();
|
||
foreach (var item in allItems)
|
||
{
|
||
logisticsItems.Add(item);
|
||
}
|
||
|
||
// 筛选适用车辆尺寸的通道
|
||
applicableChannels = CategoryAttributeManager.FilterByVehicleSize(logisticsItems, vehicleSize);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"根据车辆尺寸筛选通道失败: {ex.Message}");
|
||
}
|
||
|
||
return applicableChannels;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动选择通道
|
||
/// </summary>
|
||
/// <returns>手动选择的通道集合</returns>
|
||
public ModelItemCollection GetManuallySelectedChannels()
|
||
{
|
||
var selectedChannels = new ModelItemCollection();
|
||
|
||
try
|
||
{
|
||
// 获取当前用户选择的模型项
|
||
var currentSelection = Application.ActiveDocument.CurrentSelection.SelectedItems;
|
||
|
||
foreach (ModelItem item in currentSelection)
|
||
{
|
||
selectedChannels.Add(item);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"获取手动选择通道失败: {ex.Message}");
|
||
}
|
||
|
||
return selectedChannels;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置选中的通道为活动通道
|
||
/// </summary>
|
||
/// <param name="channels">通道集合</param>
|
||
public void SetActiveChannels(ModelItemCollection channels)
|
||
{
|
||
try
|
||
{
|
||
_selectedChannels.Clear();
|
||
|
||
foreach (ModelItem channel in channels)
|
||
{
|
||
_selectedChannels.Add(channel);
|
||
}
|
||
|
||
// 计算通道边界
|
||
CalculateCombinedBounds();
|
||
|
||
// 触发通道选择事件
|
||
ChannelsSelected?.Invoke(this, _selectedChannels);
|
||
|
||
System.Diagnostics.Debug.WriteLine($"设置了 {_selectedChannels.Count} 个活动通道");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"设置活动通道失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 高亮显示通道
|
||
/// </summary>
|
||
/// <param name="channels">要高亮的通道集合</param>
|
||
/// <param name="color">高亮颜色</param>
|
||
public void HighlightChannels(ModelItemCollection channels, System.Drawing.Color color)
|
||
{
|
||
try
|
||
{
|
||
if (channels == null || channels.Count == 0) return;
|
||
|
||
// 使用临时颜色覆盖高亮通道
|
||
var navisColor = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f);
|
||
Application.ActiveDocument.Models.OverrideTemporaryColor(channels, navisColor);
|
||
|
||
// 刷新视图
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"高亮通道失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除通道高亮
|
||
/// </summary>
|
||
public void ClearChannelHighlight()
|
||
{
|
||
try
|
||
{
|
||
// 重置所有临时材质
|
||
Application.ActiveDocument.Models.ResetAllTemporaryMaterials();
|
||
|
||
// 刷新视图
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"清除通道高亮失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示通道选择对话框
|
||
/// </summary>
|
||
/// <returns>用户选择结果</returns>
|
||
public ChannelSelectionDialogResult ShowChannelSelectionDialog()
|
||
{
|
||
var result = new ChannelSelectionDialogResult();
|
||
|
||
try
|
||
{
|
||
// 获取通道分析结果
|
||
var analysisResult = GetAllChannelSelectionResults();
|
||
|
||
// 创建通道选择对话框
|
||
var dialog = new ChannelSelectionDialog(analysisResult);
|
||
|
||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
||
{
|
||
result.Success = true;
|
||
result.SelectedChannels = dialog.SelectedChannels;
|
||
result.SelectionMethod = dialog.SelectionMethod;
|
||
result.VehicleSize = dialog.VehicleSize;
|
||
|
||
// 设置选中的通道为活动通道
|
||
SetActiveChannels(result.SelectedChannels);
|
||
}
|
||
else
|
||
{
|
||
result.Success = false;
|
||
result.Message = "用户取消了通道选择";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result.Success = false;
|
||
result.Message = $"显示通道选择对话框失败: {ex.Message}";
|
||
System.Diagnostics.Debug.WriteLine($"显示通道选择对话框失败: {ex.Message}");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取通道统计信息
|
||
/// </summary>
|
||
/// <returns>通道统计信息</returns>
|
||
public string GetChannelStatistics()
|
||
{
|
||
try
|
||
{
|
||
var analysisResult = GetAllChannelSelectionResults();
|
||
|
||
var statistics = $"通道分析统计:\n" +
|
||
$"总模型项: {analysisResult.TotalModelItems}\n" +
|
||
$"已标记物流项: {analysisResult.LogisticsMarkedItems.Count}\n" +
|
||
$"可通行区域: {analysisResult.TraversableAreas.Count}\n" +
|
||
$"通道类型项: {analysisResult.ChannelItems.Count}\n" +
|
||
$"自动检测通道: {analysisResult.AutoDetectedChannels.Count}\n" +
|
||
$"当前选中通道: {_selectedChannels.Count}";
|
||
|
||
return statistics;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return $"获取统计信息失败: {ex.Message}";
|
||
}
|
||
}
|
||
|
||
#region 路径验证与优化
|
||
|
||
/// <summary>
|
||
/// 验证路径有效性
|
||
/// </summary>
|
||
/// <param name="route">要验证的路径</param>
|
||
/// <returns>验证结果</returns>
|
||
public PathValidationResult ValidatePath(PathRoute route)
|
||
{
|
||
var result = new PathValidationResult
|
||
{
|
||
RouteId = route?.Id ?? "",
|
||
RouteName = route?.Name ?? ""
|
||
};
|
||
|
||
if (route == null)
|
||
{
|
||
result.IsValid = false;
|
||
result.Errors.Add("路径对象为空");
|
||
return result;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 基本路径验证
|
||
ValidateBasicPathStructure(route, result);
|
||
|
||
// 几何有效性验证
|
||
ValidatePathGeometry(route, result);
|
||
|
||
// 通道约束验证
|
||
ValidateChannelConstraints(route, result);
|
||
|
||
// 碰撞检测
|
||
ValidatePathCollisions(route, result);
|
||
|
||
// 可达性验证
|
||
ValidatePathReachability(route, result);
|
||
|
||
// 设置总体验证结果
|
||
result.IsValid = result.Errors.Count == 0;
|
||
|
||
if (result.IsValid)
|
||
{
|
||
result.Message = "路径验证通过";
|
||
}
|
||
else
|
||
{
|
||
result.Message = $"路径验证失败,发现 {result.Errors.Count} 个错误";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result.IsValid = false;
|
||
result.Errors.Add($"验证过程发生异常: {ex.Message}");
|
||
result.Message = "路径验证异常";
|
||
System.Diagnostics.Debug.WriteLine($"路径验证失败: {ex.Message}");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证基本路径结构
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">验证结果</param>
|
||
private void ValidateBasicPathStructure(PathRoute route, PathValidationResult result)
|
||
{
|
||
// 检查路径点数量
|
||
if (route.Points.Count < 2)
|
||
{
|
||
result.Errors.Add("路径必须至少包含2个点(起点和终点)");
|
||
return;
|
||
}
|
||
|
||
// 检查起点和终点
|
||
var startPoints = route.Points.Where(p => p.Type == PathPointType.StartPoint).ToList();
|
||
var endPoints = route.Points.Where(p => p.Type == PathPointType.EndPoint).ToList();
|
||
|
||
if (startPoints.Count == 0)
|
||
{
|
||
result.Errors.Add("路径缺少起点");
|
||
}
|
||
else if (startPoints.Count > 1)
|
||
{
|
||
result.Warnings.Add("路径包含多个起点,建议只保留一个");
|
||
}
|
||
|
||
if (endPoints.Count == 0)
|
||
{
|
||
result.Errors.Add("路径缺少终点");
|
||
}
|
||
else if (endPoints.Count > 1)
|
||
{
|
||
result.Warnings.Add("路径包含多个终点,建议只保留一个");
|
||
}
|
||
|
||
// 检查路径点索引连续性
|
||
var sortedPoints = route.GetSortedPoints();
|
||
for (int i = 0; i < sortedPoints.Count; i++)
|
||
{
|
||
if (sortedPoints[i].Index != i)
|
||
{
|
||
result.Warnings.Add($"路径点索引不连续,位置 {i} 的点索引为 {sortedPoints[i].Index}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证路径几何有效性
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">验证结果</param>
|
||
private void ValidatePathGeometry(PathRoute route, PathValidationResult result)
|
||
{
|
||
var points = route.GetSortedPoints();
|
||
|
||
// 检查重复点
|
||
for (int i = 0; i < points.Count - 1; i++)
|
||
{
|
||
var distance = CalculateDistance3D(points[i].Position, points[i + 1].Position);
|
||
if (distance < 0.01) // 1厘米以内认为重复
|
||
{
|
||
result.Warnings.Add($"检测到重复点:点 {i} 和点 {i + 1} 距离过近 ({distance:F3}m)");
|
||
}
|
||
}
|
||
|
||
// 检查路径段长度
|
||
for (int i = 0; i < points.Count - 1; i++)
|
||
{
|
||
var distance = CalculateDistance3D(points[i].Position, points[i + 1].Position);
|
||
|
||
if (distance < 0.1) // 10厘米
|
||
{
|
||
result.Warnings.Add($"路径段 {i}-{i + 1} 过短 ({distance:F3}m)");
|
||
}
|
||
else if (distance > 100.0) // 100米
|
||
{
|
||
result.Warnings.Add($"路径段 {i}-{i + 1} 过长 ({distance:F3}m),建议添加中间点");
|
||
}
|
||
}
|
||
|
||
// 检查总路径长度
|
||
if (route.TotalLength < 0.5)
|
||
{
|
||
result.Warnings.Add($"路径总长度过短 ({route.TotalLength:F3}m)");
|
||
}
|
||
else if (route.TotalLength > 1000.0)
|
||
{
|
||
result.Warnings.Add($"路径总长度过长 ({route.TotalLength:F3}m),建议分段处理");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证通道约束
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">验证结果</param>
|
||
private void ValidateChannelConstraints(PathRoute route, PathValidationResult result)
|
||
{
|
||
if (_selectedChannels == null || _selectedChannels.Count == 0)
|
||
{
|
||
result.Warnings.Add("未选择任何通道,无法验证通道约束");
|
||
return;
|
||
}
|
||
|
||
result.Warnings.Add($"当前选中通道数量: {_selectedChannels.Count}");
|
||
|
||
var points = route.GetSortedPoints();
|
||
|
||
foreach (var point in points)
|
||
{
|
||
bool isInChannel = false;
|
||
|
||
// 检查点是否在选定的通道内
|
||
foreach (ModelItem channel in _selectedChannels)
|
||
{
|
||
if (IsPointInChannel(point.Position, channel))
|
||
{
|
||
isInChannel = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!isInChannel)
|
||
{
|
||
result.Warnings.Add($"路径点 '{point.Name}' 不在选定的通道范围内");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证路径碰撞
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">验证结果</param>
|
||
private void ValidatePathCollisions(PathRoute route, PathValidationResult result)
|
||
{
|
||
try
|
||
{
|
||
var points = route.GetSortedPoints();
|
||
|
||
// 获取所有障碍物
|
||
var obstacles = GetObstacles();
|
||
|
||
if (obstacles.Count == 0)
|
||
{
|
||
result.Warnings.Add("未找到障碍物数据,无法进行碰撞检测");
|
||
return;
|
||
}
|
||
|
||
// 检查路径点碰撞
|
||
foreach (var point in points)
|
||
{
|
||
if (IsPointCollidingWithObstacles(point.Position, obstacles))
|
||
{
|
||
result.Errors.Add($"路径点 '{point.Name}' 与障碍物发生碰撞");
|
||
}
|
||
}
|
||
|
||
// 检查路径段碰撞
|
||
for (int i = 0; i < points.Count - 1; i++)
|
||
{
|
||
if (IsPathSegmentCollidingWithObstacles(points[i].Position, points[i + 1].Position, obstacles))
|
||
{
|
||
result.Errors.Add($"路径段 {i}-{i + 1} 与障碍物发生碰撞");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result.Warnings.Add($"碰撞检测失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证路径可达性
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">验证结果</param>
|
||
private void ValidatePathReachability(PathRoute route, PathValidationResult result)
|
||
{
|
||
var points = route.GetSortedPoints();
|
||
|
||
// 检查每个路径段的可达性
|
||
for (int i = 0; i < points.Count - 1; i++)
|
||
{
|
||
var startPoint = points[i].Position;
|
||
var endPoint = points[i + 1].Position;
|
||
|
||
// 简化的可达性检查:检查高度差
|
||
var heightDifference = Math.Abs(endPoint.Z - startPoint.Z);
|
||
|
||
if (heightDifference > 5.0) // 5米高度差
|
||
{
|
||
result.Warnings.Add($"路径段 {i}-{i + 1} 高度差过大 ({heightDifference:F3}m),可能需要电梯或楼梯");
|
||
}
|
||
|
||
// 检查坡度
|
||
var horizontalDistance = Math.Sqrt(
|
||
Math.Pow(endPoint.X - startPoint.X, 2) +
|
||
Math.Pow(endPoint.Y - startPoint.Y, 2)
|
||
);
|
||
|
||
if (horizontalDistance > 0.1)
|
||
{
|
||
var slope = heightDifference / horizontalDistance;
|
||
if (slope > 0.2) // 20%坡度
|
||
{
|
||
result.Warnings.Add($"路径段 {i}-{i + 1} 坡度过大 ({slope * 100:F1}%)");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 优化路径
|
||
/// </summary>
|
||
/// <param name="route">要优化的路径</param>
|
||
/// <param name="optimizationOptions">优化选项</param>
|
||
/// <returns>优化结果</returns>
|
||
public PathOptimizationResult OptimizePath(PathRoute route, PathOptimizationOptions optimizationOptions = null)
|
||
{
|
||
var result = new PathOptimizationResult
|
||
{
|
||
OriginalRoute = route,
|
||
OptimizedRoute = route.Clone()
|
||
};
|
||
|
||
if (optimizationOptions == null)
|
||
{
|
||
optimizationOptions = new PathOptimizationOptions();
|
||
}
|
||
|
||
try
|
||
{
|
||
var optimizedRoute = result.OptimizedRoute;
|
||
|
||
// 记录原始路径信息
|
||
result.OriginalLength = route.TotalLength;
|
||
result.OriginalPointCount = route.Points.Count;
|
||
|
||
// 应用各种优化策略
|
||
if (optimizationOptions.RemoveDuplicatePoints)
|
||
{
|
||
RemoveDuplicatePoints(optimizedRoute, result);
|
||
}
|
||
|
||
if (optimizationOptions.SmoothPath)
|
||
{
|
||
SmoothPath(optimizedRoute, result);
|
||
}
|
||
|
||
if (optimizationOptions.OptimizeAngles)
|
||
{
|
||
OptimizePathAngles(optimizedRoute, result);
|
||
}
|
||
|
||
if (optimizationOptions.AdjustToChannels)
|
||
{
|
||
AdjustPathToChannels(optimizedRoute, result);
|
||
}
|
||
|
||
// 重新计算优化后的路径信息
|
||
optimizedRoute.RecalculateLength();
|
||
result.OptimizedLength = optimizedRoute.TotalLength;
|
||
result.OptimizedPointCount = optimizedRoute.Points.Count;
|
||
|
||
// 计算优化效果
|
||
result.LengthReduction = result.OriginalLength - result.OptimizedLength;
|
||
result.PointReduction = result.OriginalPointCount - result.OptimizedPointCount;
|
||
|
||
result.Success = true;
|
||
result.Message = $"路径优化完成,长度减少 {result.LengthReduction:F3}m,点数减少 {result.PointReduction}";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result.Success = false;
|
||
result.Message = $"路径优化失败: {ex.Message}";
|
||
System.Diagnostics.Debug.WriteLine($"路径优化失败: {ex.Message}");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除重复点
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">优化结果</param>
|
||
private void RemoveDuplicatePoints(PathRoute route, PathOptimizationResult result)
|
||
{
|
||
var points = route.GetSortedPoints();
|
||
var pointsToRemove = new List<PathPoint>();
|
||
|
||
for (int i = 0; i < points.Count - 1; i++)
|
||
{
|
||
var distance = CalculateDistance3D(points[i].Position, points[i + 1].Position);
|
||
if (distance < 0.01) // 1厘米阈值
|
||
{
|
||
// 保留索引较小的点,移除后面的点
|
||
pointsToRemove.Add(points[i + 1]);
|
||
result.OptimizationSteps.Add($"移除重复点: {points[i + 1].Name}");
|
||
}
|
||
}
|
||
|
||
foreach (var point in pointsToRemove)
|
||
{
|
||
route.RemovePoint(point.Id);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 平滑路径
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">优化结果</param>
|
||
private void SmoothPath(PathRoute route, PathOptimizationResult result)
|
||
{
|
||
var points = route.GetSortedPoints();
|
||
if (points.Count < 3) return;
|
||
|
||
// 应用简单的移动平均平滑
|
||
for (int i = 1; i < points.Count - 1; i++)
|
||
{
|
||
var prevPoint = points[i - 1].Position;
|
||
var currentPoint = points[i].Position;
|
||
var nextPoint = points[i + 1].Position;
|
||
|
||
// 计算平滑后的位置
|
||
var smoothedPosition = new Point3D(
|
||
(prevPoint.X + currentPoint.X + nextPoint.X) / 3.0,
|
||
(prevPoint.Y + currentPoint.Y + nextPoint.Y) / 3.0,
|
||
(prevPoint.Z + currentPoint.Z + nextPoint.Z) / 3.0
|
||
);
|
||
|
||
// 更新点位置
|
||
points[i].Position = smoothedPosition;
|
||
result.OptimizationSteps.Add($"平滑点: {points[i].Name}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 优化路径角度
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">优化结果</param>
|
||
private void OptimizePathAngles(PathRoute route, PathOptimizationResult result)
|
||
{
|
||
var points = route.GetSortedPoints();
|
||
if (points.Count < 3) return;
|
||
|
||
var pointsToRemove = new List<PathPoint>();
|
||
|
||
// 检查是否有几乎共线的三个点
|
||
for (int i = 1; i < points.Count - 1; i++)
|
||
{
|
||
var p1 = points[i - 1].Position;
|
||
var p2 = points[i].Position;
|
||
var p3 = points[i + 1].Position;
|
||
|
||
// 计算角度
|
||
var angle = CalculateAngle(p1, p2, p3);
|
||
|
||
// 如果角度接近180度(共线),考虑移除中间点
|
||
if (Math.Abs(angle - Math.PI) < 0.1) // 约5.7度的容差
|
||
{
|
||
pointsToRemove.Add(points[i]);
|
||
result.OptimizationSteps.Add($"移除冗余点: {points[i].Name} (角度: {angle * 180 / Math.PI:F1}°)");
|
||
}
|
||
}
|
||
|
||
foreach (var point in pointsToRemove)
|
||
{
|
||
route.RemovePoint(point.Id);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调整路径到通道中心
|
||
/// </summary>
|
||
/// <param name="route">路径</param>
|
||
/// <param name="result">优化结果</param>
|
||
private void AdjustPathToChannels(PathRoute route, PathOptimizationResult result)
|
||
{
|
||
if (_selectedChannels.Count == 0) return;
|
||
|
||
var points = route.GetSortedPoints();
|
||
|
||
foreach (var point in points)
|
||
{
|
||
var adjustedPosition = GetOptimalPositionInChannels(point.Position);
|
||
if (adjustedPosition != null)
|
||
{
|
||
var originalPosition = point.Position;
|
||
point.Position = adjustedPosition;
|
||
|
||
var distance = CalculateDistance3D(originalPosition, adjustedPosition);
|
||
result.OptimizationSteps.Add($"调整点到通道中心: {point.Name} (移动距离: {distance:F3}m)");
|
||
}
|
||
}
|
||
}
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 递归收集所有ModelItem
|
||
/// </summary>
|
||
/// <param name="item">当前ModelItem</param>
|
||
/// <param name="collection">收集列表</param>
|
||
private void CollectAllItems(ModelItem item, List<ModelItem> collection)
|
||
{
|
||
if (item == null) return;
|
||
|
||
collection.Add(item);
|
||
|
||
if (item.Children != null && item.Children.Count() > 0)
|
||
{
|
||
foreach (ModelItem child in item.Children)
|
||
{
|
||
CollectAllItems(child, collection);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算两个3D点之间的距离
|
||
/// </summary>
|
||
/// <param name="point1">第一个点</param>
|
||
/// <param name="point2">第二个点</param>
|
||
/// <returns>距离</returns>
|
||
private double CalculateDistance3D(Point3D point1, Point3D point2)
|
||
{
|
||
if (point1 == null || point2 == null)
|
||
throw new ArgumentNullException("坐标点不能为空");
|
||
|
||
double dx = point2.X - point1.X;
|
||
double dy = point2.Y - point1.Y;
|
||
double dz = point2.Z - point1.Z;
|
||
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查点是否在通道内
|
||
/// </summary>
|
||
/// <param name="point">点位置</param>
|
||
/// <param name="channel">通道模型项</param>
|
||
/// <returns>是否在通道内</returns>
|
||
private bool IsPointInChannel(Point3D point, ModelItem channel)
|
||
{
|
||
try
|
||
{
|
||
var boundingBox = channel.BoundingBox();
|
||
if (boundingBox == null) return false;
|
||
|
||
return point.X >= boundingBox.Min.X && point.X <= boundingBox.Max.X &&
|
||
point.Y >= boundingBox.Min.Y && point.Y <= boundingBox.Max.Y &&
|
||
point.Z >= boundingBox.Min.Z && point.Z <= boundingBox.Max.Z;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取障碍物集合
|
||
/// </summary>
|
||
/// <returns>障碍物集合</returns>
|
||
private ModelItemCollection GetObstacles()
|
||
{
|
||
var obstacles = new ModelItemCollection();
|
||
|
||
try
|
||
{
|
||
// 获取所有标记为障碍物的模型项
|
||
var allItems = new List<ModelItem>();
|
||
foreach (ModelItem rootItem in Application.ActiveDocument.Models.RootItems)
|
||
{
|
||
CollectAllItems(rootItem, allItems);
|
||
}
|
||
var filteredItems = allItems.Where(item => item.HasGeometry && CategoryAttributeManager.HasLogisticsAttributes(item)).ToArray();
|
||
|
||
var logisticsItems = new ModelItemCollection();
|
||
foreach (var item in filteredItems)
|
||
{
|
||
logisticsItems.Add(item);
|
||
}
|
||
|
||
obstacles = CategoryAttributeManager.FilterByLogisticsType(
|
||
logisticsItems, CategoryAttributeManager.LogisticsElementType.障碍物);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"获取障碍物失败: {ex.Message}");
|
||
}
|
||
|
||
return obstacles;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查点是否与障碍物碰撞
|
||
/// </summary>
|
||
/// <param name="point">点位置</param>
|
||
/// <param name="obstacles">障碍物集合</param>
|
||
/// <returns>是否碰撞</returns>
|
||
private bool IsPointCollidingWithObstacles(Point3D point, ModelItemCollection obstacles)
|
||
{
|
||
foreach (ModelItem obstacle in obstacles)
|
||
{
|
||
if (IsPointInChannel(point, obstacle))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查路径段是否与障碍物碰撞
|
||
/// </summary>
|
||
/// <param name="startPoint">起点</param>
|
||
/// <param name="endPoint">终点</param>
|
||
/// <param name="obstacles">障碍物集合</param>
|
||
/// <returns>是否碰撞</returns>
|
||
private bool IsPathSegmentCollidingWithObstacles(Point3D startPoint, Point3D endPoint, ModelItemCollection obstacles)
|
||
{
|
||
// 简化的线段碰撞检测:在线段上采样多个点进行检查
|
||
const int sampleCount = 10;
|
||
|
||
for (int i = 0; i <= sampleCount; i++)
|
||
{
|
||
var t = (double)i / sampleCount;
|
||
var samplePoint = new Point3D(
|
||
startPoint.X + t * (endPoint.X - startPoint.X),
|
||
startPoint.Y + t * (endPoint.Y - startPoint.Y),
|
||
startPoint.Z + t * (endPoint.Z - startPoint.Z)
|
||
);
|
||
|
||
if (IsPointCollidingWithObstacles(samplePoint, obstacles))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算三点间的角度
|
||
/// </summary>
|
||
/// <param name="p1">第一个点</param>
|
||
/// <param name="p2">中间点</param>
|
||
/// <param name="p3">第三个点</param>
|
||
/// <returns>角度(弧度)</returns>
|
||
private double CalculateAngle(Point3D p1, Point3D p2, Point3D p3)
|
||
{
|
||
var v1 = new Point3D(p1.X - p2.X, p1.Y - p2.Y, p1.Z - p2.Z);
|
||
var v2 = new Point3D(p3.X - p2.X, p3.Y - p2.Y, p3.Z - p2.Z);
|
||
|
||
var dotProduct = v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z;
|
||
var length1 = Math.Sqrt(v1.X * v1.X + v1.Y * v1.Y + v1.Z * v1.Z);
|
||
var length2 = Math.Sqrt(v2.X * v2.X + v2.Y * v2.Y + v2.Z * v2.Z);
|
||
|
||
if (length1 == 0 || length2 == 0) return 0;
|
||
|
||
var cosAngle = dotProduct / (length1 * length2);
|
||
cosAngle = Math.Max(-1, Math.Min(1, cosAngle)); // 限制在[-1,1]范围内
|
||
|
||
return Math.Acos(cosAngle);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取在通道中的最优位置
|
||
/// </summary>
|
||
/// <param name="originalPosition">原始位置</param>
|
||
/// <returns>最优位置</returns>
|
||
private Point3D GetOptimalPositionInChannels(Point3D originalPosition)
|
||
{
|
||
// 简化实现:返回最近通道的中心点
|
||
Point3D bestPosition = originalPosition;
|
||
double minDistance = double.MaxValue;
|
||
|
||
foreach (ModelItem channel in _selectedChannels)
|
||
{
|
||
var boundingBox = channel.BoundingBox();
|
||
if (boundingBox != null)
|
||
{
|
||
var centerPoint = new Point3D(
|
||
(boundingBox.Min.X + boundingBox.Max.X) / 2,
|
||
(boundingBox.Min.Y + boundingBox.Max.Y) / 2,
|
||
originalPosition.Z // 保持原始高度
|
||
);
|
||
|
||
var distance = CalculateDistance3D(originalPosition, centerPoint);
|
||
if (distance < minDistance)
|
||
{
|
||
minDistance = distance;
|
||
bestPosition = centerPoint;
|
||
}
|
||
}
|
||
}
|
||
|
||
return bestPosition;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取Navisworks文档单位并转换为米的系数
|
||
/// </summary>
|
||
/// <returns>转换系数(文档单位转换为米)</returns>
|
||
private double GetUnitsToMetersConversionFactor()
|
||
{
|
||
try
|
||
{
|
||
var units = Application.ActiveDocument.Units;
|
||
|
||
switch (units)
|
||
{
|
||
case Units.Millimeters:
|
||
return 0.001;
|
||
case Units.Centimeters:
|
||
return 0.01;
|
||
case Units.Meters:
|
||
return 1.0;
|
||
case Units.Inches:
|
||
return 0.0254;
|
||
case Units.Feet:
|
||
return 0.3048;
|
||
case Units.Kilometers:
|
||
return 1000.0;
|
||
case Units.Micrometers:
|
||
return 0.000001;
|
||
case Units.Microinches:
|
||
return 0.0000000254;
|
||
case Units.Mils:
|
||
return 0.0000254;
|
||
case Units.Yards:
|
||
return 0.9144;
|
||
case Units.Miles:
|
||
return 1609.43;
|
||
default:
|
||
return 1.0;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"获取文档单位失败: {ex.Message},默认为米");
|
||
return 1.0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 转换包围盒到米单位
|
||
/// </summary>
|
||
/// <param name="boundingBox">原始包围盒</param>
|
||
/// <param name="conversionFactor">转换系数</param>
|
||
/// <returns>转换后的包围盒(米单位)</returns>
|
||
private BoundingBox3D ConvertBoundingBoxToMeters(BoundingBox3D boundingBox, double conversionFactor)
|
||
{
|
||
var minPoint = new Point3D(
|
||
boundingBox.Min.X * conversionFactor,
|
||
boundingBox.Min.Y * conversionFactor,
|
||
boundingBox.Min.Z * conversionFactor
|
||
);
|
||
var maxPoint = new Point3D(
|
||
boundingBox.Max.X * conversionFactor,
|
||
boundingBox.Max.Y * conversionFactor,
|
||
boundingBox.Max.Z * conversionFactor
|
||
);
|
||
return new BoundingBox3D(minPoint, maxPoint);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 新增:3D交互模式相关
|
||
|
||
/// <summary>
|
||
/// 高亮显示选中的通道
|
||
/// </summary>
|
||
/// <param name="highlightColor">高亮颜色,为null时使用默认颜色</param>
|
||
/// <returns>是否成功高亮</returns>
|
||
public bool HighlightSelectedChannels(System.Drawing.Color? highlightColor = null)
|
||
{
|
||
try
|
||
{
|
||
if (_selectedChannels.Count == 0)
|
||
{
|
||
OnErrorOccurred("没有选择任何通道,请先选择通道");
|
||
return false;
|
||
}
|
||
|
||
WriteLog($"[高亮] 开始高亮 {_selectedChannels.Count} 个通道");
|
||
|
||
// 使用明显的高亮颜色 - 纯绿色,不透明
|
||
var color = highlightColor ?? System.Drawing.Color.Green;
|
||
|
||
// 转换为Navisworks颜色
|
||
var navisColor = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f);
|
||
|
||
WriteLog($"[高亮] 使用颜色: R={navisColor.R}, G={navisColor.G}, B={navisColor.B}");
|
||
|
||
// 创建ModelItemCollection
|
||
var itemsToHighlight = new ModelItemCollection();
|
||
foreach (var channel in _selectedChannels)
|
||
{
|
||
itemsToHighlight.Add(channel);
|
||
WriteLog($"[高亮] 添加通道: {channel.DisplayName}");
|
||
}
|
||
|
||
// 先清除之前的高亮
|
||
Application.ActiveDocument.Models.ResetAllTemporaryMaterials();
|
||
|
||
// 应用临时颜色覆盖
|
||
Application.ActiveDocument.Models.OverrideTemporaryColor(itemsToHighlight, navisColor);
|
||
|
||
// 强制刷新视图
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
|
||
OnStatusChanged($"已高亮显示 {_selectedChannels.Count} 个通道");
|
||
WriteLog($"[高亮] 高亮完成");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
WriteLog($"[高亮] 高亮失败: {ex.Message}");
|
||
OnErrorOccurred($"高亮通道失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除通道高亮显示
|
||
/// </summary>
|
||
/// <returns>是否成功清除</returns>
|
||
public bool ClearChannelHighlighting()
|
||
{
|
||
try
|
||
{
|
||
// 重置所有临时材质
|
||
Application.ActiveDocument.Models.ResetAllTemporaryMaterials();
|
||
|
||
// 刷新视图
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
|
||
OnStatusChanged("已清除通道高亮");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"清除通道高亮失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 进入3D路径编辑模式
|
||
/// </summary>
|
||
/// <returns>是否成功进入编辑模式</returns>
|
||
public bool EnterPathEditMode()
|
||
{
|
||
try
|
||
{
|
||
if (_selectedChannels.Count == 0)
|
||
{
|
||
OnErrorOccurred("请先选择通道再进入路径编辑模式");
|
||
return false;
|
||
}
|
||
|
||
_isPathEditMode = true;
|
||
_globalIsPathEditMode = true; // 设置全局标志
|
||
|
||
// 设置活动管理器
|
||
_activePathManager = this;
|
||
|
||
// 初始化当前路径(如果没有的话)
|
||
if (_currentRoute == null)
|
||
{
|
||
_currentRoute = CreateNewRoute("路径1");
|
||
}
|
||
|
||
// 重置点类型为起点(准备接收第一个点)
|
||
_currentPointType = PathPointType.StartPoint;
|
||
|
||
// 高亮显示通道
|
||
HighlightSelectedChannels();
|
||
|
||
// 启动3D点击监听
|
||
Start3DClickListener();
|
||
|
||
// 触发模式变更事件
|
||
PathEditModeChanged?.Invoke(this, true);
|
||
|
||
OnStatusChanged("已进入3D路径编辑模式,下一个点将设为起点");
|
||
WriteLog($"[模式] 已进入路径编辑模式,当前点类型: {GetPointTypeName(_currentPointType)}");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"进入路径编辑模式失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 退出3D路径编辑模式
|
||
/// </summary>
|
||
/// <returns>是否成功退出编辑模式</returns>
|
||
public bool ExitPathEditMode()
|
||
{
|
||
try
|
||
{
|
||
// 将最后一个点设为终点(如果有路径点的话)
|
||
if (_currentRoute != null && _currentRoute.Points != null && _currentRoute.Points.Count > 0)
|
||
{
|
||
var lastPoint = _currentRoute.Points[_currentRoute.Points.Count - 1];
|
||
if (lastPoint.Type != PathPointType.EndPoint)
|
||
{
|
||
// 更新最后一个点为终点
|
||
var updatedPoint = new PathPoint
|
||
{
|
||
Name = lastPoint.Name.Replace("路径点", "终点").Replace("起点", "终点"),
|
||
Position = lastPoint.Position,
|
||
Type = PathPointType.EndPoint,
|
||
CreatedTime = lastPoint.CreatedTime
|
||
};
|
||
|
||
// 替换最后一个点
|
||
_currentRoute.Points[_currentRoute.Points.Count - 1] = updatedPoint;
|
||
|
||
// 重新绘制3D标记以更新颜色
|
||
Clear3DPathMarkers();
|
||
foreach (var point in _currentRoute.Points)
|
||
{
|
||
Draw3DPathPoint(point);
|
||
}
|
||
|
||
WriteLog($"[模式] 已将最后一个点设为终点: {updatedPoint.Name}");
|
||
}
|
||
}
|
||
|
||
_isPathEditMode = false;
|
||
_globalIsPathEditMode = false; // 清除全局标志
|
||
|
||
// 停止3D点击监听
|
||
Stop3DClickListener();
|
||
|
||
// 清除通道高亮
|
||
ClearChannelHighlighting();
|
||
|
||
// 触发模式变更事件
|
||
PathEditModeChanged?.Invoke(this, false);
|
||
|
||
OnStatusChanged("已退出3D路径编辑模式,最后一个点已设为终点");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"退出路径编辑模式失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在3D视图中添加路径点
|
||
/// </summary>
|
||
/// <param name="worldPoint">3D世界坐标</param>
|
||
/// <param name="pointType">路径点类型,为null时使用当前类型</param>
|
||
/// <returns>添加的路径点,失败时返回null</returns>
|
||
public PathPoint AddPathPointIn3D(Point3D worldPoint, PathPointType? pointType = null)
|
||
{
|
||
try
|
||
{
|
||
if (!_isPathEditMode)
|
||
{
|
||
OnErrorOccurred("请先进入路径编辑模式");
|
||
return null;
|
||
}
|
||
|
||
var actualPointType = pointType ?? _currentPointType;
|
||
|
||
// 验证点是否在通道范围内
|
||
if (!IsPointInSelectedChannels(worldPoint))
|
||
{
|
||
OnErrorOccurred("路径点必须设置在选中的通道内");
|
||
return null;
|
||
}
|
||
|
||
// 创建路径点
|
||
var pointName = GeneratePointName(actualPointType);
|
||
var pathPoint = new PathPoint(worldPoint, pointName, actualPointType);
|
||
|
||
// 添加到当前路径
|
||
if (_currentRoute == null)
|
||
{
|
||
_currentRoute = new PathRoute("默认路径");
|
||
_routes.Add(_currentRoute);
|
||
}
|
||
|
||
_currentRoute.AddPoint(pathPoint);
|
||
|
||
// 在3D视图中绘制路径点标记
|
||
Draw3DPathPoint(pathPoint);
|
||
|
||
// 触发事件
|
||
PathPointAddedIn3D?.Invoke(this, pathPoint);
|
||
// PointAdded?.Invoke(this, pathPoint); // TODO: 定义PointAdded事件
|
||
|
||
OnStatusChanged($"已添加{GetPointTypeName(actualPointType)}: {pointName}");
|
||
return pathPoint;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"添加3D路径点失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查点是否在选中的通道内
|
||
/// </summary>
|
||
/// <param name="worldPoint">世界坐标点</param>
|
||
/// <returns>是否在通道内</returns>
|
||
private bool IsPointInSelectedChannels(Point3D worldPoint)
|
||
{
|
||
try
|
||
{
|
||
foreach (var channel in _selectedChannels)
|
||
{
|
||
var boundingBox = channel.BoundingBox();
|
||
if (boundingBox != null)
|
||
{
|
||
if (worldPoint.X >= boundingBox.Min.X && worldPoint.X <= boundingBox.Max.X &&
|
||
worldPoint.Y >= boundingBox.Min.Y && worldPoint.Y <= boundingBox.Max.Y &&
|
||
worldPoint.Z >= boundingBox.Min.Z && worldPoint.Z <= boundingBox.Max.Z)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
catch
|
||
{
|
||
return true; // 如果检查失败,允许设置点(宽容处理)
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成路径点名称
|
||
/// </summary>
|
||
/// <param name="pointType">点类型</param>
|
||
/// <returns>生成的名称</returns>
|
||
private string GeneratePointName(PathPointType pointType)
|
||
{
|
||
var currentPoints = _currentRoute?.Points ?? new List<PathPoint>();
|
||
var typeCount = currentPoints.Count(p => p.Type == pointType) + 1;
|
||
|
||
switch (pointType)
|
||
{
|
||
case PathPointType.StartPoint:
|
||
return $"起点{typeCount}";
|
||
case PathPointType.EndPoint:
|
||
return $"终点{typeCount}";
|
||
case PathPointType.WayPoint:
|
||
return $"路径点{typeCount}";
|
||
default:
|
||
return $"点{typeCount}";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在3D视图中绘制路径点标记(简化版本)
|
||
/// </summary>
|
||
/// <param name="pathPoint">路径点</param>
|
||
private void Draw3DPathPoint(PathPoint pathPoint)
|
||
{
|
||
try
|
||
{
|
||
// TODO: 实现Navisworks 2017兼容的3D标记绘制
|
||
// 当前版本使用日志记录代替实际绘制
|
||
var colorName = GetPointColorName(pathPoint.Type);
|
||
WriteLog($"[3D标记] {pathPoint.Name} ({pathPoint.Type}) at ({pathPoint.Position.X:F2}, {pathPoint.Position.Y:F2}, {pathPoint.Position.Z:F2}) - {colorName}");
|
||
|
||
// 可选:使用临时颜色高亮附近的模型项来间接标记位置
|
||
HighlightNearbyItems(pathPoint.Position, GetPointColor(pathPoint.Type));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
WriteLog($"绘制3D路径点失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取点类型对应的颜色名称
|
||
/// </summary>
|
||
/// <param name="pointType">路径点类型</param>
|
||
/// <returns>颜色名称</returns>
|
||
private string GetPointColorName(PathPointType pointType)
|
||
{
|
||
switch (pointType)
|
||
{
|
||
case PathPointType.StartPoint:
|
||
return "绿色";
|
||
case PathPointType.EndPoint:
|
||
return "红色";
|
||
default:
|
||
return "蓝色";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取点类型对应的Navisworks颜色
|
||
/// </summary>
|
||
/// <param name="pointType">路径点类型</param>
|
||
/// <returns>Navisworks颜色</returns>
|
||
private Color GetPointColor(PathPointType pointType)
|
||
{
|
||
switch (pointType)
|
||
{
|
||
case PathPointType.StartPoint:
|
||
return Color.Green;
|
||
case PathPointType.EndPoint:
|
||
return Color.Red;
|
||
default:
|
||
return Color.Blue;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 高亮显示路径点附近的模型项
|
||
/// </summary>
|
||
/// <param name="position">位置</param>
|
||
/// <param name="color">高亮颜色</param>
|
||
private void HighlightNearbyItems(Point3D position, Color color)
|
||
{
|
||
try
|
||
{
|
||
// 简化实现:在路径点位置附近查找模型项并高亮
|
||
// 这是一个间接的方式来标记路径点位置
|
||
foreach (var channel in _selectedChannels)
|
||
{
|
||
var boundingBox = channel.BoundingBox();
|
||
if (boundingBox != null && IsPointInBoundingBox(position, boundingBox))
|
||
{
|
||
var itemCollection = new ModelItemCollection();
|
||
itemCollection.Add(channel);
|
||
Application.ActiveDocument.Models.OverrideTemporaryColor(itemCollection, color);
|
||
break; // 只高亮第一个匹配的通道
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"高亮附近项目失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查点是否在包围盒内
|
||
/// </summary>
|
||
/// <param name="point">点位置</param>
|
||
/// <param name="boundingBox">包围盒</param>
|
||
/// <returns>是否在包围盒内</returns>
|
||
private bool IsPointInBoundingBox(Point3D point, BoundingBox3D boundingBox)
|
||
{
|
||
return point.X >= boundingBox.Min.X && point.X <= boundingBox.Max.X &&
|
||
point.Y >= boundingBox.Min.Y && point.Y <= boundingBox.Max.Y &&
|
||
point.Z >= boundingBox.Min.Z && point.Z <= boundingBox.Max.Z;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 获取路径点类型的中文名称
|
||
/// </summary>
|
||
/// <param name="pointType">路径点类型</param>
|
||
/// <returns>中文名称</returns>
|
||
private string GetPointTypeName(PathPointType pointType)
|
||
{
|
||
switch (pointType)
|
||
{
|
||
case PathPointType.StartPoint:
|
||
return "起点";
|
||
case PathPointType.EndPoint:
|
||
return "终点";
|
||
case PathPointType.WayPoint:
|
||
return "路径点";
|
||
default:
|
||
return "路径点";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除所有3D路径标记
|
||
/// </summary>
|
||
public void Clear3DPathMarkers()
|
||
{
|
||
try
|
||
{
|
||
// 清除所有临时材质和颜色覆盖
|
||
Application.ActiveDocument.Models.ResetAllTemporaryMaterials();
|
||
|
||
// 刷新视图
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
|
||
OnStatusChanged("已清除所有3D路径标记");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"清除3D路径标记失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 3D点击监听功能
|
||
|
||
// 3D点击监听相关
|
||
private System.Windows.Forms.Timer _clickListenerTimer;
|
||
private static PathPlanningManager _activePathManager; // 静态引用,用于处理全局点击事件
|
||
|
||
/// <summary>
|
||
/// 启动3D点击监听
|
||
/// </summary>
|
||
private void Start3DClickListener()
|
||
{
|
||
try
|
||
{
|
||
WriteLog($"[监听] 启动3D点击监听");
|
||
|
||
// 设置当前实例为活动管理器
|
||
_activePathManager = this;
|
||
|
||
// 创建定时器来定期检查鼠标点击
|
||
_clickListenerTimer = new System.Windows.Forms.Timer();
|
||
_clickListenerTimer.Interval = 100; // 100ms检查一次
|
||
_clickListenerTimer.Tick += ClickListenerTimer_Tick;
|
||
_clickListenerTimer.Start();
|
||
|
||
WriteLog($"[监听] 定时器已启动,间隔: {_clickListenerTimer.Interval}ms");
|
||
OnStatusChanged("3D点击监听已启动,请在高亮的通道上点击设置路径点");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
WriteLog($"[监听] 启动失败: {ex.Message}");
|
||
OnErrorOccurred($"启动3D点击监听失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止3D点击监听
|
||
/// </summary>
|
||
private void Stop3DClickListener()
|
||
{
|
||
try
|
||
{
|
||
if (_clickListenerTimer != null)
|
||
{
|
||
_clickListenerTimer.Stop();
|
||
_clickListenerTimer.Dispose();
|
||
_clickListenerTimer = null;
|
||
}
|
||
|
||
// 清除活动管理器引用
|
||
if (_activePathManager == this)
|
||
{
|
||
_activePathManager = null;
|
||
}
|
||
|
||
OnStatusChanged("3D点击监听已停止");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"停止3D点击监听失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 定时器事件处理器 - 检查鼠标点击
|
||
/// </summary>
|
||
private void ClickListenerTimer_Tick(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 检查当前是否有选择的模型项(通过鼠标点击产生)
|
||
var currentSelection = Application.ActiveDocument.CurrentSelection.SelectedItems;
|
||
|
||
if (currentSelection.Count > 0)
|
||
{
|
||
WriteLog($"[点击监听] 检测到选择项: {currentSelection.Count} 个");
|
||
|
||
// 获取第一个选中的项目
|
||
ModelItem selectedItem = currentSelection.First();
|
||
WriteLog($"[点击监听] 选中项: {selectedItem.DisplayName}");
|
||
|
||
// 检查是否点击在选定的通道内
|
||
if (IsItemInSelectedChannels(selectedItem))
|
||
{
|
||
WriteLog($"[点击监听] 点击在通道内,准备添加路径点");
|
||
|
||
// 获取点击位置(使用选中项的包围盒中心作为近似位置)
|
||
var boundingBox = selectedItem.BoundingBox();
|
||
if (boundingBox != null)
|
||
{
|
||
var clickPosition = new Point3D(
|
||
(boundingBox.Min.X + boundingBox.Max.X) / 2,
|
||
(boundingBox.Min.Y + boundingBox.Max.Y) / 2,
|
||
(boundingBox.Min.Z + boundingBox.Max.Z) / 2
|
||
);
|
||
|
||
WriteLog($"[点击监听] 点击位置: ({clickPosition.X:F2}, {clickPosition.Y:F2}, {clickPosition.Z:F2})");
|
||
|
||
// 在添加点之前确定正确的点类型
|
||
PathPointType pointTypeToUse;
|
||
if (_currentRoute == null || _currentRoute.Points.Count == 0)
|
||
{
|
||
pointTypeToUse = PathPointType.StartPoint; // 第一个点
|
||
}
|
||
else
|
||
{
|
||
pointTypeToUse = PathPointType.WayPoint; // 后续点都是路径点
|
||
}
|
||
|
||
// 添加路径点
|
||
var pathPoint = AddPathPointIn3D(clickPosition, pointTypeToUse);
|
||
|
||
if (pathPoint != null)
|
||
{
|
||
WriteLog($"[点击监听] 成功添加路径点: {pathPoint.Name}");
|
||
|
||
// 清除当前选择,为下次点击做准备
|
||
Application.ActiveDocument.CurrentSelection.Clear();
|
||
|
||
// 更新当前点类型状态
|
||
_currentPointType = pointTypeToUse;
|
||
}
|
||
else
|
||
{
|
||
WriteLog($"[点击监听] 添加路径点失败");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
WriteLog($"[点击监听] 无法获取选中项的包围盒");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
WriteLog($"[点击监听] 点击不在通道内,清除选择");
|
||
// 点击了非通道区域,清除选择并提示
|
||
Application.ActiveDocument.CurrentSelection.Clear();
|
||
OnStatusChanged("请点击高亮的通道区域设置路径点");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
WriteLog($"[点击监听] 监听错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查选中的项目是否在选定的通道中
|
||
/// </summary>
|
||
/// <param name="item">选中的模型项</param>
|
||
/// <returns>是否在通道中</returns>
|
||
private bool IsItemInSelectedChannels(ModelItem item)
|
||
{
|
||
return _selectedChannels.Contains(item) || IsItemChildOfSelectedChannels(item);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查项目是否为选定通道的子项
|
||
/// </summary>
|
||
/// <param name="item">要检查的项目</param>
|
||
/// <returns>是否为子项</returns>
|
||
private bool IsItemChildOfSelectedChannels(ModelItem item)
|
||
{
|
||
foreach (var channel in _selectedChannels)
|
||
{
|
||
if (IsChildOf(item, channel))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归检查是否为子项
|
||
/// </summary>
|
||
/// <param name="child">子项</param>
|
||
/// <param name="parent">父项</param>
|
||
/// <returns>是否为子项</returns>
|
||
private bool IsChildOf(ModelItem child, ModelItem parent)
|
||
{
|
||
var currentParent = child.Parent;
|
||
while (currentParent != null)
|
||
{
|
||
if (currentParent == parent)
|
||
{
|
||
return true;
|
||
}
|
||
currentParent = currentParent.Parent;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动切换点类型
|
||
/// </summary>
|
||
private void AutoSwitchPointType()
|
||
{
|
||
try
|
||
{
|
||
if (_currentRoute == null || _currentRoute.Points == null)
|
||
return;
|
||
|
||
// 根据当前路径点数量自动设置点类型
|
||
int pointCount = _currentRoute.Points.Count;
|
||
|
||
if (pointCount == 0)
|
||
{
|
||
// 第一个点设为起点
|
||
_currentPointType = PathPointType.StartPoint;
|
||
}
|
||
else if (pointCount == 1 && _currentPointType == PathPointType.StartPoint)
|
||
{
|
||
// 第二个点开始设为路径点
|
||
_currentPointType = PathPointType.WayPoint;
|
||
OnStatusChanged("下一个点将设为路径点,退出编辑时最后一个点自动设为终点");
|
||
}
|
||
// 其他情况保持路径点类型
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
WriteLog($"自动切换点类型失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 静态方法:获取当前活动的路径管理器
|
||
/// </summary>
|
||
/// <returns>当前活动的路径管理器</returns>
|
||
public static PathPlanningManager GetActivePathManager()
|
||
{
|
||
return _activePathManager;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动设置当前点类型(供外部调用)
|
||
/// </summary>
|
||
/// <param name="pointType">要设置的点类型</param>
|
||
public void SetCurrentPointType(PathPointType pointType)
|
||
{
|
||
_currentPointType = pointType;
|
||
OnStatusChanged($"已切换到{GetPointTypeName(pointType)}模式");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#endregion
|
||
}
|
||
} |