2961 lines
111 KiB
C#
2961 lines
111 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Autodesk.Navisworks.Api;
|
||
using Autodesk.Navisworks.Api.Plugins;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// 路径规划管理器
|
||
/// 负责路径规划的核心业务逻辑
|
||
/// </summary>
|
||
public class PathPlanningManager
|
||
{
|
||
private CategoryAttributeManager _categoryManager;
|
||
private VisibilityManager _visibilityManager;
|
||
private CoordinateConverter _coordinateConverter;
|
||
private NavigationMapWindow _mapWindow;
|
||
private PathVisualizer _pathVisualizer;
|
||
private PathPointRenderPlugin _renderPlugin;
|
||
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;
|
||
|
||
// 日志管理已统一到LogManager类
|
||
|
||
/// <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));
|
||
_pathVisualizer = new PathVisualizer();
|
||
|
||
// 获取已注册的圆形渲染插件实例
|
||
try
|
||
{
|
||
// 等待RenderPlugin自动注册,然后获取静态实例
|
||
_renderPlugin = PathPointRenderPlugin.Instance;
|
||
if (_renderPlugin != null)
|
||
{
|
||
LogManager.WriteLog("[路径管理] PathPointRenderPlugin实例获取成功");
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[路径管理] PathPointRenderPlugin实例尚未就绪,将在后续尝试获取");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[路径管理] PathPointRenderPlugin实例获取失败: {ex.Message}");
|
||
_renderPlugin = null;
|
||
}
|
||
|
||
_selectedChannels = new List<ModelItem>();
|
||
_routes = new List<PathRoute>();
|
||
_currentRoute = new PathRoute("默认路径");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 无参构造函数(为向后兼容而保留)
|
||
/// </summary>
|
||
public PathPlanningManager()
|
||
{
|
||
_categoryManager = new CategoryAttributeManager();
|
||
_visibilityManager = new VisibilityManager();
|
||
_pathVisualizer = new PathVisualizer();
|
||
|
||
// 获取已注册的圆形渲染插件实例
|
||
try
|
||
{
|
||
// 等待RenderPlugin自动注册,然后获取静态实例
|
||
_renderPlugin = PathPointRenderPlugin.Instance;
|
||
if (_renderPlugin != null)
|
||
{
|
||
LogManager.WriteLog("[路径管理] PathPointRenderPlugin实例获取成功(无参构造)");
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[路径管理] PathPointRenderPlugin实例尚未就绪(无参构造)");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[路径管理] PathPointRenderPlugin实例获取失败(无参构造): {ex.Message}");
|
||
_renderPlugin = null;
|
||
}
|
||
|
||
_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
|
||
{
|
||
// 清理圆形渲染插件
|
||
if (_renderPlugin != null)
|
||
{
|
||
_renderPlugin.CleanUp();
|
||
_renderPlugin = null;
|
||
LogManager.WriteLog("[路径管理] PathPointRenderPlugin已清理");
|
||
}
|
||
|
||
// 停用ToolPlugin
|
||
if (_isToolPluginActive)
|
||
{
|
||
DeactivateToolPlugin();
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
// 日志方法已移动到LogManager类
|
||
|
||
#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>
|
||
/// <summary>
|
||
/// 检查ModelItem是否有效且未被释放
|
||
/// </summary>
|
||
/// <param name="item">要检查的ModelItem</param>
|
||
/// <returns>是否有效</returns>
|
||
private bool IsModelItemValid(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
if (item == null) return false;
|
||
|
||
// 尝试访问基本属性来检查对象是否有效
|
||
var name = item.DisplayName;
|
||
var hasGeometry = item.HasGeometry;
|
||
return true;
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
return false;
|
||
}
|
||
catch (Exception)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查Application和Document是否有效且未被释放
|
||
/// </summary>
|
||
/// <returns>是否有效</returns>
|
||
private bool IsApplicationDocumentValid()
|
||
{
|
||
try
|
||
{
|
||
if (Application.ActiveDocument == null) return false;
|
||
if (Application.ActiveDocument.CurrentSelection == null) return false;
|
||
|
||
// 尝试访问基本属性来检查对象是否有效
|
||
var fileName = Application.ActiveDocument.FileName;
|
||
var selectionCount = Application.ActiveDocument.CurrentSelection.SelectedItems.Count;
|
||
return true;
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
return false;
|
||
}
|
||
catch (Exception)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 安全地清除当前选择
|
||
/// </summary>
|
||
/// <returns>是否成功清除</returns>
|
||
private bool SafelyClearSelection()
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog("[SafelyClearSelection] 方法开始");
|
||
|
||
LogManager.WriteLog("[SafelyClearSelection] 开始检查Application状态");
|
||
if (!IsApplicationDocumentValid())
|
||
{
|
||
LogManager.WriteLog("[选择清除] Application或Document对象无效,跳过清除");
|
||
LogManager.WriteLog("[SafelyClearSelection] 方法结束(状态无效)");
|
||
return false;
|
||
}
|
||
LogManager.WriteLog("[SafelyClearSelection] Application状态检查通过");
|
||
|
||
LogManager.WriteLog("[SafelyClearSelection] 开始调用Application.ActiveDocument.CurrentSelection.Clear()");
|
||
Application.ActiveDocument.CurrentSelection.Clear();
|
||
LogManager.WriteLog("[SafelyClearSelection] Clear()调用完成");
|
||
LogManager.WriteLog("[选择清除] 成功清除选择");
|
||
LogManager.WriteLog("[SafelyClearSelection] 方法结束(成功)");
|
||
return true;
|
||
}
|
||
catch (ObjectDisposedException ex)
|
||
{
|
||
LogManager.WriteLog($"[选择清除] 对象已释放: {ex.Message}");
|
||
LogManager.WriteLog($"[SafelyClearSelection] ObjectDisposedException堆栈: {ex.StackTrace}");
|
||
LogManager.WriteLog("[SafelyClearSelection] 方法结束(对象已释放异常)");
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[选择清除] 清除失败: {ex.Message}");
|
||
LogManager.WriteLog($"[选择清除] 异常类型: {ex.GetType().Name}");
|
||
LogManager.WriteLog($"[SafelyClearSelection] 异常堆栈: {ex.StackTrace}");
|
||
LogManager.WriteLog("[SafelyClearSelection] 方法结束(异常)");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public bool HighlightSelectedChannels(System.Drawing.Color? highlightColor = null)
|
||
{
|
||
try
|
||
{
|
||
if (_selectedChannels == null || _selectedChannels.Count == 0)
|
||
{
|
||
LogManager.WriteLog("[高亮] 没有选择任何通道");
|
||
OnErrorOccurred("没有选择任何通道,请先选择通道");
|
||
return false;
|
||
}
|
||
|
||
LogManager.WriteLog($"[高亮] 开始高亮 {_selectedChannels.Count} 个通道");
|
||
|
||
// 过滤出有效的通道
|
||
var validChannels = _selectedChannels.Where(IsModelItemValid).ToList();
|
||
if (validChannels.Count == 0)
|
||
{
|
||
LogManager.WriteLog("[高亮] 没有有效的通道对象可以高亮");
|
||
OnErrorOccurred("选中的通道对象已失效,请重新选择通道");
|
||
return false;
|
||
}
|
||
|
||
LogManager.WriteLog($"[高亮] 有效通道数量: {validChannels.Count}/{_selectedChannels.Count}");
|
||
|
||
// 使用明显的高亮颜色 - 鲜艳的绿色
|
||
var color = highlightColor ?? System.Drawing.Color.LimeGreen;
|
||
|
||
// 转换为Navisworks颜色
|
||
var navisColor = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f);
|
||
|
||
LogManager.WriteLog($"[高亮] 使用颜色: R={navisColor.R:F3}, G={navisColor.G:F3}, B={navisColor.B:F3}");
|
||
|
||
// 创建ModelItemCollection
|
||
var itemsToHighlight = new ModelItemCollection();
|
||
int addedCount = 0;
|
||
foreach (var channel in validChannels)
|
||
{
|
||
try
|
||
{
|
||
itemsToHighlight.Add(channel);
|
||
addedCount++;
|
||
LogManager.WriteLog($"[高亮] 添加通道 {addedCount}: {channel.DisplayName}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[高亮] 添加通道失败: {ex.Message}");
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (addedCount == 0)
|
||
{
|
||
LogManager.WriteLog("[高亮] 没有成功添加的通道可以高亮");
|
||
return false;
|
||
}
|
||
|
||
// 先清除之前的高亮
|
||
Application.ActiveDocument.Models.ResetAllTemporaryMaterials();
|
||
LogManager.WriteLog("[高亮] 已清除之前的高亮");
|
||
|
||
// 应用临时颜色覆盖
|
||
Application.ActiveDocument.Models.OverrideTemporaryColor(itemsToHighlight, navisColor);
|
||
LogManager.WriteLog($"[高亮] 已应用颜色覆盖到 {addedCount} 个通道");
|
||
|
||
// 强制刷新视图
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
LogManager.WriteLog("[高亮] 已请求视图刷新");
|
||
|
||
OnStatusChanged($"已高亮显示 {addedCount} 个通道");
|
||
LogManager.WriteLog($"[高亮] 高亮完成,成功处理 {addedCount} 个通道");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[高亮] 高亮失败: {ex.Message}");
|
||
LogManager.WriteLog($"[高亮] 异常堆栈: {ex.StackTrace}");
|
||
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()
|
||
{
|
||
LogManager.WriteLog("★★★ EnterPathEditMode方法被调用 ★★★");
|
||
try
|
||
{
|
||
LogManager.Info("[测试] EnterPathEditMode方法被调用");
|
||
LogManager.WriteLog("[路径编辑] ===== 进入路径编辑模式 =====");
|
||
|
||
if (!IsApplicationDocumentValid())
|
||
{
|
||
LogManager.WriteLog("[路径编辑] ✗ 应用程序状态无效");
|
||
return false;
|
||
}
|
||
|
||
if (_selectedChannels == null || !_selectedChannels.Any())
|
||
{
|
||
LogManager.WriteLog("[路径编辑] ✗ 未选择通道");
|
||
return false;
|
||
}
|
||
|
||
LogManager.WriteLog($"[路径编辑] 已选择通道数量: {_selectedChannels.Count}");
|
||
|
||
// 设置路径编辑模式
|
||
_isPathEditMode = true;
|
||
_globalIsPathEditMode = true;
|
||
_activePathManager = this;
|
||
|
||
LogManager.WriteLog("[路径编辑] ✓ 路径编辑模式状态已设置");
|
||
|
||
// 高亮显示选中的通道
|
||
if (HighlightSelectedChannels())
|
||
{
|
||
LogManager.WriteLog("[路径编辑] ✓ 通道高亮显示成功");
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[路径编辑] ✗ 通道高亮显示失败");
|
||
}
|
||
|
||
// 激活ToolPlugin进行精确点击检测(必须成功)
|
||
LogManager.WriteLog("[路径编辑] 启动ToolPlugin精确点击检测");
|
||
if (!ActivateToolPlugin())
|
||
{
|
||
LogManager.WriteLog("[路径编辑] ✗ ToolPlugin激活失败");
|
||
_isPathEditMode = false;
|
||
_globalIsPathEditMode = false;
|
||
OnErrorOccurred("ToolPlugin激活失败,无法进入3D路径编辑模式");
|
||
return false;
|
||
}
|
||
LogManager.WriteLog("[路径编辑] ✓ ToolPlugin激活成功");
|
||
|
||
// 触发事件
|
||
PathEditModeChanged?.Invoke(this, true);
|
||
OnStatusChanged($"路径编辑模式已启动 - 点击通道添加路径点 (当前类型: {GetPointTypeName(_currentPointType)})");
|
||
|
||
LogManager.WriteLog("[路径编辑] ===== 路径编辑模式启动完成 =====");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[路径编辑] 进入路径编辑模式异常: {ex.Message}");
|
||
LogManager.WriteLog($"[路径编辑] 异常堆栈: {ex.StackTrace}");
|
||
|
||
// 异常恢复
|
||
_isPathEditMode = false;
|
||
_globalIsPathEditMode = false;
|
||
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);
|
||
}
|
||
|
||
LogManager.WriteLog($"[模式] 已将最后一个点设为终点: {updatedPoint.Name}");
|
||
}
|
||
}
|
||
|
||
_isPathEditMode = false;
|
||
_globalIsPathEditMode = false; // 清除全局标志
|
||
|
||
// 停用ToolPlugin
|
||
if (_isToolPluginActive)
|
||
{
|
||
LogManager.WriteLog("[路径编辑] 停用ToolPlugin");
|
||
DeactivateToolPlugin();
|
||
}
|
||
|
||
// 清除通道高亮
|
||
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
|
||
{
|
||
LogManager.WriteLog($"[路径点计算] 开始计算路径点,输入坐标: ({worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2})");
|
||
|
||
if (!_isPathEditMode)
|
||
{
|
||
LogManager.WriteLog($"[路径点计算] 错误:未进入路径编辑模式");
|
||
OnErrorOccurred("请先进入路径编辑模式");
|
||
return null;
|
||
}
|
||
|
||
var actualPointType = pointType ?? _currentPointType;
|
||
LogManager.WriteLog($"[路径点计算] 确定点类型: {actualPointType} (原始类型: {pointType}, 当前类型: {_currentPointType})");
|
||
|
||
// 验证点是否在通道范围内
|
||
LogManager.WriteLog($"[路径点计算] 开始验证点是否在通道范围内,通道数量: {_selectedChannels?.Count ?? 0}");
|
||
if (!IsPointInSelectedChannels(worldPoint))
|
||
{
|
||
LogManager.WriteLog($"[路径点计算] 错误:路径点不在选中的通道内");
|
||
OnErrorOccurred("路径点必须设置在选中的通道内");
|
||
return null;
|
||
}
|
||
LogManager.WriteLog($"[路径点计算] 通道范围验证成功");
|
||
|
||
// 创建路径点
|
||
var pointName = GeneratePointName(actualPointType);
|
||
LogManager.WriteLog($"[路径点计算] 生成点名称: {pointName}");
|
||
|
||
var pathPoint = new PathPoint(worldPoint, pointName, actualPointType);
|
||
LogManager.WriteLog($"[路径点计算] 创建路径点对象成功: {pathPoint.Name}, 类型: {pathPoint.Type}");
|
||
|
||
// 添加到当前路径
|
||
if (_currentRoute == null)
|
||
{
|
||
_currentRoute = new PathRoute("默认路径");
|
||
_routes.Add(_currentRoute);
|
||
LogManager.WriteLog($"[路径点计算] 创建新的默认路径");
|
||
}
|
||
|
||
var beforeCount = _currentRoute.Points.Count;
|
||
_currentRoute.AddPoint(pathPoint);
|
||
LogManager.WriteLog($"[路径点计算] 添加到路径,路径点数量: {beforeCount} -> {_currentRoute.Points.Count}");
|
||
|
||
// 在3D视图中绘制路径点标记
|
||
LogManager.WriteLog($"[路径点计算] 开始绘制3D路径点标记");
|
||
Draw3DPathPoint(pathPoint);
|
||
|
||
// 触发事件
|
||
LogManager.WriteLog($"[路径点计算] 触发PathPointAddedIn3D事件");
|
||
PathPointAddedIn3D?.Invoke(this, pathPoint);
|
||
// PointAdded?.Invoke(this, pathPoint); // TODO: 定义PointAdded事件
|
||
|
||
LogManager.WriteLog($"[路径点计算] 计算完成,成功添加路径点: {pointName}");
|
||
OnStatusChanged($"已添加{GetPointTypeName(actualPointType)}: {pointName}");
|
||
return pathPoint;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[路径点计算] 异常: {ex.Message}");
|
||
LogManager.WriteLog($"[路径点计算] 异常堆栈: {ex.StackTrace}");
|
||
OnErrorOccurred($"添加3D路径点失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查点是否在选中的通道内
|
||
/// </summary>
|
||
/// <param name="worldPoint">世界坐标点</param>
|
||
/// <returns>是否在通道内</returns>
|
||
private bool IsPointInSelectedChannels(Point3D worldPoint)
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog($"[通道验证] 检查点 ({worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2}) 是否在通道内");
|
||
|
||
if (_selectedChannels == null || _selectedChannels.Count == 0)
|
||
{
|
||
LogManager.WriteLog($"[通道验证] 警告:没有选中的通道,跳过验证");
|
||
return true; // 如果没有选中通道,允许设置点
|
||
}
|
||
|
||
for (int i = 0; i < _selectedChannels.Count; i++)
|
||
{
|
||
var channel = _selectedChannels[i];
|
||
LogManager.WriteLog($"[通道验证] 检查通道 {i + 1}/{_selectedChannels.Count}: {channel?.DisplayName ?? "Unknown"}");
|
||
|
||
var boundingBox = channel.BoundingBox();
|
||
if (boundingBox != null)
|
||
{
|
||
LogManager.WriteLog($"[通道验证] 通道包围盒: Min({boundingBox.Min.X:F2}, {boundingBox.Min.Y:F2}, {boundingBox.Min.Z:F2}) Max({boundingBox.Max.X:F2}, {boundingBox.Max.Y:F2}, {boundingBox.Max.Z:F2})");
|
||
|
||
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)
|
||
{
|
||
LogManager.WriteLog($"[通道验证] 点在通道 {i + 1} 内,验证通过");
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog($"[通道验证] 点不在通道 {i + 1} 内");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog($"[通道验证] 通道 {i + 1} 包围盒为空");
|
||
}
|
||
}
|
||
|
||
LogManager.WriteLog($"[通道验证] 点不在任何选中通道内,验证失败");
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[通道验证] 异常: {ex.Message},允许设置点(宽容处理)");
|
||
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
|
||
{
|
||
var pointNumber = GetPathPointNumber(pathPoint);
|
||
|
||
LogManager.WriteLog($"[3D标记] 开始绘制路径点标记: {pathPoint.Name}");
|
||
|
||
// 1. 创建圆形标记(如果RenderPlugin可用)
|
||
if (_renderPlugin == null)
|
||
{
|
||
// 尝试重新获取RenderPlugin实例
|
||
_renderPlugin = PathPointRenderPlugin.Instance;
|
||
}
|
||
|
||
if (_renderPlugin != null)
|
||
{
|
||
_renderPlugin.AddCircleMarker(pathPoint.Position, pathPoint.Type, pointNumber);
|
||
LogManager.WriteLog($"[3D标记] 圆形标记创建成功");
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog($"[3D标记] 圆形标记不可用,RenderPlugin未注册");
|
||
}
|
||
|
||
// 2. 创建文本标注序号
|
||
CreateTextLabel(pathPoint.Position, pointNumber.ToString(), pathPoint);
|
||
|
||
var colorName = GetPointColorName(pathPoint.Type);
|
||
LogManager.WriteLog($"[3D标记] {pathPoint.Name} (序号:{pointNumber}) at ({pathPoint.Position.X:F2}, {pathPoint.Position.Y:F2}, {pathPoint.Position.Z:F2}) - {colorName} [圆形标记系统]");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[3D标记] 绘制3D路径点失败: {ex.Message}");
|
||
LogManager.WriteLog($"[3D标记] 异常堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 创建文本标注
|
||
/// </summary>
|
||
/// <param name="position">标注位置</param>
|
||
/// <param name="text">标注文本</param>
|
||
/// <param name="pathPoint">关联的路径点</param>
|
||
private void CreateTextLabel(Point3D position, string text, PathPoint pathPoint)
|
||
{
|
||
try
|
||
{
|
||
// 注意:Navisworks 2017的文本标注API可能有限
|
||
// 这里使用日志记录作为临时实现,后续可改进为真正的3D文本
|
||
LogManager.WriteLog($"[文本标注] 在位置 ({position.X:F2}, {position.Y:F2}, {position.Z:F2}) 创建标注: \"{text}\"");
|
||
|
||
// 记录文本标注信息
|
||
if (_pathPointMarkers == null)
|
||
_pathPointMarkers = new List<PathPointMarker>();
|
||
|
||
_pathPointMarkers.Add(new PathPointMarker
|
||
{
|
||
PathPoint = pathPoint,
|
||
LabelText = text,
|
||
LabelPosition = position,
|
||
MarkerType = PathPointMarkerType.TextLabel
|
||
});
|
||
|
||
// TODO: 实现真正的3D文本标注(如果Navisworks 2017 API支持)
|
||
// 可能的实现方式:
|
||
// 1. 使用COM接口的标注功能
|
||
// 2. 创建简单的几何文本
|
||
// 3. 使用视点注释功能
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[文本标注] 创建文本标注失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径点在当前路径中的序号
|
||
/// </summary>
|
||
/// <param name="pathPoint">路径点</param>
|
||
/// <returns>序号(从1开始)</returns>
|
||
private int GetPathPointNumber(PathPoint pathPoint)
|
||
{
|
||
if (_currentRoute?.Points != null)
|
||
{
|
||
var index = _currentRoute.Points.IndexOf(pathPoint);
|
||
return index >= 0 ? index + 1 : 0;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/// <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>
|
||
/// 获取点类型对应的Navisworks API颜色(用于PathVisualizer)
|
||
/// </summary>
|
||
/// <param name="pointType">路径点类型</param>
|
||
/// <returns>Navisworks API颜色</returns>
|
||
private Autodesk.Navisworks.Api.Color GetNavisworksColor(PathPointType pointType)
|
||
{
|
||
switch (pointType)
|
||
{
|
||
case PathPointType.StartPoint:
|
||
return Autodesk.Navisworks.Api.Color.Green;
|
||
case PathPointType.EndPoint:
|
||
return Autodesk.Navisworks.Api.Color.Red;
|
||
default:
|
||
return Autodesk.Navisworks.Api.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
|
||
{
|
||
LogManager.WriteLog("[3D标记清理] 开始清除所有路径点标记");
|
||
|
||
// 1. 清除圆形标记(如果RenderPlugin可用)
|
||
if (_renderPlugin != null)
|
||
{
|
||
var circleCount = _renderPlugin.MarkerCount;
|
||
_renderPlugin.ClearAllMarkers();
|
||
LogManager.WriteLog($"[3D标记清理] 清除 {circleCount} 个圆形标记");
|
||
}
|
||
|
||
// 2. 清除文本标注记录
|
||
if (_pathPointMarkers != null)
|
||
{
|
||
var labelMarkers = _pathPointMarkers.Where(m => m.MarkerType == PathPointMarkerType.TextLabel).ToList();
|
||
foreach (var marker in labelMarkers)
|
||
{
|
||
LogManager.WriteLog($"[3D标记清理] 清除路径点 {marker.PathPoint?.Name} 的文本标注: {marker.LabelText}");
|
||
// TODO: 清除实际的3D文本标注(当实现后)
|
||
}
|
||
|
||
LogManager.WriteLog($"[3D标记清理] 共清除 {labelMarkers.Count} 个文本标注记录");
|
||
|
||
// 清空标记列表
|
||
_pathPointMarkers.Clear();
|
||
}
|
||
|
||
// 3. 刷新视图
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All);
|
||
|
||
LogManager.WriteLog("[3D标记清理] 3D路径标记清除完成");
|
||
OnStatusChanged("已清除所有3D路径标记");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[3D标记清理] 清除3D路径标记失败: {ex.Message}");
|
||
OnErrorOccurred($"清除3D路径标记失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 3D路径编辑辅助功能
|
||
|
||
private static PathPlanningManager _activePathManager; // 静态引用,用于处理ToolPlugin事件
|
||
|
||
|
||
|
||
|
||
|
||
/// <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
|
||
{
|
||
LogManager.WriteLog("[AutoSwitchPointType] 方法开始");
|
||
|
||
if (_currentRoute == null || _currentRoute.Points == null)
|
||
{
|
||
LogManager.WriteLog("[AutoSwitchPointType] 当前路径或路径点为空,返回");
|
||
return;
|
||
}
|
||
|
||
LogManager.WriteLog($"[AutoSwitchPointType] 当前路径: {_currentRoute.Name}, 当前点类型: {_currentPointType}");
|
||
|
||
// 根据当前路径点数量自动设置点类型
|
||
int pointCount = _currentRoute.Points.Count;
|
||
LogManager.WriteLog($"[AutoSwitchPointType] 当前路径点数量: {pointCount}");
|
||
|
||
if (pointCount == 0)
|
||
{
|
||
// 第一个点设为起点
|
||
LogManager.WriteLog("[AutoSwitchPointType] 设置为起点类型");
|
||
_currentPointType = PathPointType.StartPoint;
|
||
}
|
||
else if (pointCount == 1 && _currentPointType == PathPointType.StartPoint)
|
||
{
|
||
// 第二个点开始设为路径点
|
||
LogManager.WriteLog("[AutoSwitchPointType] 从起点切换为路径点类型");
|
||
_currentPointType = PathPointType.WayPoint;
|
||
LogManager.WriteLog("[AutoSwitchPointType] 点类型已切换为路径点");
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog($"[AutoSwitchPointType] 保持当前点类型: {_currentPointType}");
|
||
}
|
||
// 其他情况保持路径点类型
|
||
|
||
LogManager.WriteLog("[AutoSwitchPointType] 方法完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[AutoSwitchPointType] 自动切换点类型失败: {ex.Message}");
|
||
LogManager.WriteLog($"[AutoSwitchPointType] 异常堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
/// <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
|
||
|
||
#region ToolPlugin 集成
|
||
|
||
/// <summary>
|
||
/// ToolPlugin是否已激活
|
||
/// </summary>
|
||
private bool _isToolPluginActive = false;
|
||
|
||
/// <summary>
|
||
/// 激活自定义ToolPlugin进行精确点击检测
|
||
/// </summary>
|
||
private bool ActivateToolPlugin()
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin] ===== 开始激活ToolPlugin =====");
|
||
LogManager.WriteLog($"[ToolPlugin] 当前应用程序状态: {Application.IsAutomated}");
|
||
LogManager.WriteLog($"[ToolPlugin] 当前文档状态: {Application.ActiveDocument?.Title ?? "NULL"}");
|
||
|
||
// 1. 加载插件程序集
|
||
LogManager.WriteLog("[ToolPlugin] 步骤1: 加载插件程序集");
|
||
var assemblyPath = PathClickToolPlugin.AssemblyPath;
|
||
LogManager.WriteLog($"[ToolPlugin] 程序集路径: {assemblyPath}");
|
||
LogManager.WriteLog($"[ToolPlugin] 程序集文件是否存在: {System.IO.File.Exists(assemblyPath)}");
|
||
|
||
Application.Plugins.AddPluginAssembly(assemblyPath);
|
||
LogManager.WriteLog("[ToolPlugin] ✓ 程序集加载完成");
|
||
|
||
// 2. 查找插件
|
||
LogManager.WriteLog("[ToolPlugin] 步骤2: 查找插件");
|
||
ToolPluginRecord toolPluginRecord = (ToolPluginRecord)Application.Plugins.FindPlugin("PathClickTool.NavisworksTransport");
|
||
|
||
if (toolPluginRecord == null)
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin] ✗ 错误: 无法找到PathClickTool插件");
|
||
return false;
|
||
}
|
||
LogManager.WriteLog("[ToolPlugin] ✓ 插件查找成功");
|
||
|
||
// 3. 加载插件
|
||
LogManager.WriteLog("[ToolPlugin] 步骤3: 加载插件");
|
||
var loadedPlugin = toolPluginRecord.LoadPlugin();
|
||
if (loadedPlugin == null)
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin] ✗ 错误: 插件加载失败");
|
||
return false;
|
||
}
|
||
LogManager.WriteLog("[ToolPlugin] ✓ 插件加载成功");
|
||
|
||
// 4. 设置为活动工具
|
||
LogManager.WriteLog("[ToolPlugin] 步骤4: 设置为活动工具");
|
||
Application.MainDocument.Tool.SetCustomToolPlugin(loadedPlugin);
|
||
LogManager.WriteLog("[ToolPlugin] ✓ 工具设置成功");
|
||
|
||
// 5. 订阅点击事件
|
||
LogManager.WriteLog("[ToolPlugin] 步骤5: 订阅点击事件");
|
||
PathClickToolPlugin.MouseClicked += OnToolPluginMouseClicked;
|
||
LogManager.WriteLog("[ToolPlugin] ✓ 事件订阅成功");
|
||
|
||
_isToolPluginActive = true;
|
||
LogManager.WriteLog("[ToolPlugin] ===== ToolPlugin激活完成 =====");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[ToolPlugin] 激活异常: {ex.Message}");
|
||
LogManager.WriteLog($"[ToolPlugin] 堆栈: {ex.StackTrace}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停用ToolPlugin
|
||
/// </summary>
|
||
private bool DeactivateToolPlugin()
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin] ===== 开始停用ToolPlugin =====");
|
||
|
||
if (_isToolPluginActive)
|
||
{
|
||
// 取消订阅事件
|
||
LogManager.WriteLog("[ToolPlugin] 取消事件订阅");
|
||
PathClickToolPlugin.MouseClicked -= OnToolPluginMouseClicked;
|
||
|
||
// 恢复默认选择工具
|
||
LogManager.WriteLog("[ToolPlugin] 恢复默认选择工具");
|
||
Application.MainDocument.Tool.Value = Tool.Select;
|
||
|
||
_isToolPluginActive = false;
|
||
LogManager.WriteLog("[ToolPlugin] ✓ ToolPlugin停用完成");
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin] ToolPlugin未激活,无需停用");
|
||
}
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[ToolPlugin] 停用异常: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理ToolPlugin的鼠标点击事件
|
||
/// </summary>
|
||
private void OnToolPluginMouseClicked(object sender, PickItemResult pickResult)
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin事件] ===== 收到精确点击坐标 =====");
|
||
LogManager.WriteLog($"[ToolPlugin事件] 精确坐标: ({pickResult.Point.X:F3}, {pickResult.Point.Y:F3}, {pickResult.Point.Z:F3})");
|
||
LogManager.WriteLog($"[ToolPlugin事件] 选中对象: {pickResult.ModelItem?.DisplayName ?? "NULL"}");
|
||
|
||
// 检查是否在选定的通道内
|
||
if (_selectedChannels != null && _selectedChannels.Any())
|
||
{
|
||
bool isInChannel = IsItemInSelectedChannels(pickResult.ModelItem) ||
|
||
IsItemChildOfSelectedChannels(pickResult.ModelItem);
|
||
|
||
LogManager.WriteLog($"[ToolPlugin事件] 在选定通道内: {isInChannel}");
|
||
|
||
if (isInChannel)
|
||
{
|
||
// 使用精确的点击坐标添加路径点
|
||
LogManager.WriteLog($"[ToolPlugin事件] 添加路径点,类型: {_currentPointType}");
|
||
var pathPoint = AddPathPointIn3D(pickResult.Point, _currentPointType);
|
||
|
||
if (pathPoint != null)
|
||
{
|
||
LogManager.WriteLog($"[ToolPlugin事件] ✓ 路径点添加成功: {pathPoint.Name}");
|
||
|
||
// 自动切换点类型
|
||
AutoSwitchPointType();
|
||
LogManager.WriteLog($"[ToolPlugin事件] 下一个点类型: {_currentPointType}");
|
||
|
||
// 触发事件通知UI更新
|
||
PathPointAddedIn3D?.Invoke(this, pathPoint);
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin事件] ✗ 路径点添加失败");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin事件] ✗ 点击位置不在选定通道内");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin事件] ✗ 未选择通道");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[ToolPlugin事件] 处理异常: {ex.Message}");
|
||
LogManager.WriteLog($"[ToolPlugin事件] 堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#endregion
|
||
|
||
// 路径点3D标记管理
|
||
private List<PathPointMarker> _pathPointMarkers;
|
||
}
|
||
} |