3847 lines
146 KiB
C#
3847 lines
146 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 PathVisualizer _pathVisualizer;
|
||
private PathPointRenderPlugin _renderPlugin;
|
||
private List<ModelItem> _selectedChannels;
|
||
private List<PathRoute> _routes;
|
||
private PathRoute _currentRoute;
|
||
private ChannelBounds _combinedChannelBounds;
|
||
|
||
// 新增:路径编辑状态管理
|
||
private PathEditState _pathEditState = PathEditState.Viewing;
|
||
private PathPointType _currentPointType = PathPointType.WayPoint;
|
||
private PathRoute _editingRoute = null; // 当前正在编辑的路径
|
||
private PathHistoryManager _historyManager; // 路径历史记录管理器
|
||
|
||
// 日志管理已统一到LogManager类
|
||
|
||
/// <summary>
|
||
/// 当前路径编辑状态
|
||
/// </summary>
|
||
public PathEditState PathEditState
|
||
{
|
||
get { return _pathEditState; }
|
||
private set
|
||
{
|
||
_pathEditState = value;
|
||
PathEditStateChanged?.Invoke(this, _pathEditState);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前正在编辑的路径
|
||
/// </summary>
|
||
public PathRoute EditingRoute
|
||
{
|
||
get { return _editingRoute; }
|
||
private set { _editingRoute = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否处于可编辑状态(新建或编辑模式)
|
||
/// </summary>
|
||
public bool IsInEditableState
|
||
{
|
||
get { return _pathEditState == PathEditState.Creating || _pathEditState == PathEditState.Editing; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换到查看状态
|
||
/// </summary>
|
||
public void SwitchToViewingState()
|
||
{
|
||
PathEditState = PathEditState.Viewing;
|
||
EditingRoute = null;
|
||
|
||
// 智能管理ToolPlugin状态
|
||
ManageToolPluginForEditState();
|
||
|
||
// 清理高亮
|
||
ClearChannelHighlight();
|
||
|
||
LogManager.Info("已切换到查看状态");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换到编辑状态
|
||
/// </summary>
|
||
/// <param name="route">要编辑的路径</param>
|
||
public void SwitchToEditingState(PathRoute route)
|
||
{
|
||
if (route == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(route));
|
||
}
|
||
|
||
PathEditState = PathEditState.Editing;
|
||
EditingRoute = route;
|
||
CurrentRoute = route;
|
||
|
||
// 智能管理ToolPlugin状态
|
||
ManageToolPluginForEditState();
|
||
|
||
// 高亮通道
|
||
HighlightLogisticsChannels();
|
||
|
||
LogManager.Info($"已切换到编辑状态,正在编辑路径: {route.Name}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 完成当前编辑并保存
|
||
/// </summary>
|
||
/// <returns>是否成功完成编辑</returns>
|
||
public bool FinishEditing()
|
||
{
|
||
if (!IsInEditableState)
|
||
{
|
||
LogManager.Warning("当前不在编辑状态,无法完成编辑");
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 自动设置最后一个点为终点
|
||
if (CurrentRoute != null && CurrentRoute.Points.Count > 1)
|
||
{
|
||
var lastPoint = CurrentRoute.Points.Last();
|
||
if (lastPoint.Type != PathPointType.StartPoint) // 起点不能是终点
|
||
{
|
||
lastPoint.Type = PathPointType.EndPoint;
|
||
LogManager.Info($"已自动设置最后一个点为终点: {lastPoint.Name}");
|
||
|
||
// 更新3D标记的外观
|
||
UpdateMarkerAppearance(lastPoint);
|
||
}
|
||
}
|
||
|
||
// 如果是创建模式,将当前路径添加到路径集合
|
||
if (_pathEditState == PathEditState.Creating && CurrentRoute != null)
|
||
{
|
||
if (!_routes.Contains(CurrentRoute))
|
||
{
|
||
_routes.Add(CurrentRoute);
|
||
LogManager.Info($"新路径已添加到路径集合: {CurrentRoute.Name}");
|
||
|
||
// 添加历史记录
|
||
var historyEntry = new PathHistoryEntry(
|
||
CurrentRoute.Id,
|
||
PathHistoryOperationType.Created,
|
||
CurrentRoute,
|
||
$"创建新路径: {CurrentRoute.Name}");
|
||
_historyManager.AddHistoryEntry(historyEntry);
|
||
}
|
||
}
|
||
|
||
// 如果是编辑模式,保存编辑历史
|
||
if (_pathEditState == PathEditState.Editing && EditingRoute != null)
|
||
{
|
||
var historyEntry = new PathHistoryEntry(
|
||
EditingRoute.Id,
|
||
PathHistoryOperationType.Edited,
|
||
EditingRoute,
|
||
$"编辑路径: {EditingRoute.Name}");
|
||
_historyManager.AddHistoryEntry(historyEntry);
|
||
}
|
||
|
||
// 切换回查看状态
|
||
SwitchToViewingState();
|
||
|
||
// 触发路径点列表更新事件,以刷新终点显示
|
||
PathPointsListUpdated?.Invoke(this, CurrentRoute);
|
||
|
||
// 触发路径生成事件
|
||
RouteGenerated?.Invoke(this, CurrentRoute);
|
||
|
||
LogManager.Info("路径编辑已完成");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"完成编辑时发生错误: {ex.Message}");
|
||
OnErrorOccurred($"完成编辑失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消当前编辑
|
||
/// </summary>
|
||
/// <returns>是否成功取消编辑</returns>
|
||
public bool CancelEditing()
|
||
{
|
||
if (!IsInEditableState)
|
||
{
|
||
LogManager.Warning("当前不在编辑状态,无法取消编辑");
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 如果是创建模式,清理当前路径
|
||
if (_pathEditState == PathEditState.Creating && CurrentRoute != null)
|
||
{
|
||
CurrentRoute.Points.Clear();
|
||
Clear3DPathMarkers();
|
||
LogManager.Info("已清理新建路径的临时数据");
|
||
}
|
||
|
||
// 如果是编辑模式,恢复原始数据(这里需要实现备份恢复机制)
|
||
if (_pathEditState == PathEditState.Editing && EditingRoute != null)
|
||
{
|
||
// TODO: 实现路径数据恢复机制
|
||
LogManager.Info("已取消路径编辑(数据恢复功能待实现)");
|
||
}
|
||
|
||
// 切换回查看状态
|
||
SwitchToViewingState();
|
||
|
||
LogManager.Info("路径编辑已取消");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"取消编辑时发生错误: {ex.Message}");
|
||
OnErrorOccurred($"取消编辑失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置路径编辑状态(用于异常恢复)
|
||
/// </summary>
|
||
public void ResetPathEditState()
|
||
{
|
||
try
|
||
{
|
||
PathEditState = PathEditState.Viewing;
|
||
EditingRoute = null;
|
||
|
||
// 智能管理ToolPlugin状态(强制停用)
|
||
ManageToolPluginForEditState();
|
||
|
||
Clear3DPathMarkers();
|
||
ClearChannelHighlight();
|
||
LogManager.Info("路径编辑状态已重置");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"重置路径编辑状态时发生错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动保存当前路径到历史记录
|
||
/// </summary>
|
||
/// <param name="description">保存描述</param>
|
||
/// <returns>是否保存成功</returns>
|
||
public bool SaveCurrentRouteToHistory(string description = "手动保存")
|
||
{
|
||
try
|
||
{
|
||
if (CurrentRoute == null)
|
||
{
|
||
LogManager.Warning("当前没有路径可保存");
|
||
return false;
|
||
}
|
||
|
||
var historyEntry = new PathHistoryEntry(
|
||
CurrentRoute.Id,
|
||
PathHistoryOperationType.ManualSave,
|
||
CurrentRoute,
|
||
description);
|
||
_historyManager.AddHistoryEntry(historyEntry);
|
||
|
||
LogManager.Info($"路径已保存到历史记录: {CurrentRoute.Name}");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"保存路径到历史记录时发生错误: {ex.Message}");
|
||
OnErrorOccurred($"保存历史记录失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前设置的路径点类型
|
||
/// </summary>
|
||
public PathPointType CurrentPointType
|
||
{
|
||
get { return _currentPointType; }
|
||
set { _currentPointType = value; }
|
||
}
|
||
|
||
// 新增事件
|
||
public event EventHandler<PathEditState> PathEditStateChanged;
|
||
public event EventHandler<PathPoint> PathPointAddedIn3D;
|
||
public event EventHandler<PathPoint> PathPointRemovedFrom3D;
|
||
public event EventHandler<PathRoute> PathPointsListUpdated;
|
||
|
||
/// <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;
|
||
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();
|
||
|
||
// 设置静态引用,确保所有地方都使用同一个实例
|
||
_activePathManager = this;
|
||
LogManager.WriteLog("[路径管理] 设置_activePathManager静态引用");
|
||
|
||
// 获取已注册的圆形渲染插件实例
|
||
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("默认路径");
|
||
_historyManager = new PathHistoryManager(50); // 最多保存50个历史记录
|
||
}
|
||
|
||
/// <summary>
|
||
/// 无参构造函数(为向后兼容而保留)
|
||
/// </summary>
|
||
public PathPlanningManager()
|
||
{
|
||
_categoryManager = new CategoryAttributeManager();
|
||
_visibilityManager = new VisibilityManager();
|
||
_pathVisualizer = new PathVisualizer();
|
||
|
||
// 设置静态引用,确保所有地方都使用同一个实例
|
||
_activePathManager = this;
|
||
LogManager.WriteLog("[路径管理] 设置_activePathManager静态引用(无参构造)");
|
||
|
||
// 获取已注册的圆形渲染插件实例
|
||
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("默认路径");
|
||
_historyManager = new PathHistoryManager(50); // 最多保存50个历史记录
|
||
}
|
||
|
||
/// <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 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>
|
||
/// 添加路径到管理器(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>
|
||
/// <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();
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略清理错误
|
||
}
|
||
}
|
||
|
||
|
||
|
||
#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}";
|
||
|
||
// 添加历史记录
|
||
if (route != null)
|
||
{
|
||
var historyEntry = new PathHistoryEntry(
|
||
route.Id,
|
||
PathHistoryOperationType.Optimized,
|
||
route,
|
||
$"路径优化: 长度减少{result.LengthReduction:F3}m,点数减少{result.PointReduction}");
|
||
_historyManager.AddHistoryEntry(historyEntry);
|
||
}
|
||
}
|
||
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>
|
||
|
||
|
||
/// <summary>
|
||
/// 在3D视图中添加路径点
|
||
/// </summary>
|
||
/// <param name="worldPoint">3D世界坐标</param>
|
||
/// <param name="pointType">路径点类型,为null时使用当前类型</param>
|
||
/// <returns>添加的路径点,失败时返回null</returns>
|
||
/// <summary>
|
||
/// 在3D视图中添加路径点(增强版,支持智能通道检测)
|
||
/// </summary>
|
||
/// <param name="worldPoint">3D世界坐标</param>
|
||
/// <param name="pointType">路径点类型,为null时使用当前类型</param>
|
||
/// <returns>添加的路径点,失败时返回null</returns>
|
||
public PathPoint AddPathPointIn3D(Point3D worldPoint, PathPointType? pointType = null)
|
||
{
|
||
// 确保在编辑状态下才能添加点
|
||
if (!IsInEditableState)
|
||
{
|
||
LogManager.Warning("不在编辑状态,无法添加路径点");
|
||
OnStatusChanged("请先进入新建或编辑模式");
|
||
return null;
|
||
}
|
||
|
||
// 如果没有当前路径,则无法添加
|
||
if (CurrentRoute == null)
|
||
{
|
||
LogManager.Error("当前路径(CurrentRoute)为null,无法添加路径点");
|
||
OnErrorOccurred("内部错误:当前路径丢失");
|
||
return null;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 确定路径点类型
|
||
PathPointType finalPointType;
|
||
if (pointType.HasValue)
|
||
{
|
||
finalPointType = pointType.Value;
|
||
}
|
||
else
|
||
{
|
||
// 自动判断类型:第一个点为起点,其余为路径点
|
||
finalPointType = (CurrentRoute.Points.Count == 0)
|
||
? PathPointType.StartPoint
|
||
: PathPointType.WayPoint;
|
||
}
|
||
|
||
// 创建路径点
|
||
var pathPoint = new PathPoint
|
||
{
|
||
Name = GeneratePointName(finalPointType),
|
||
Position = worldPoint,
|
||
Type = finalPointType
|
||
};
|
||
|
||
CurrentRoute.Points.Add(pathPoint);
|
||
LogManager.Info($"路径点已添加: {pathPoint.Name}, 位置: ({worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2})");
|
||
|
||
// 在3D视图中绘制标记
|
||
Draw3DPathPoint(pathPoint);
|
||
|
||
// 触发路径点添加事件
|
||
PathPointAddedIn3D?.Invoke(this, pathPoint);
|
||
|
||
// 触发路径列表更新事件
|
||
PathPointsListUpdated?.Invoke(this, CurrentRoute);
|
||
|
||
return pathPoint;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"添加3D路径点失败: {ex.Message}");
|
||
OnErrorOccurred($"添加路径点失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行智能通道检测
|
||
/// </summary>
|
||
/// <param name="worldPoint">检测点的世界坐标</param>
|
||
/// <returns>检测结果</returns>
|
||
private ChannelDetectionResult PerformIntelligentChannelDetection(Point3D worldPoint)
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog($"[智能检测] 开始智能通道检测");
|
||
|
||
// 1. 如果有预选通道,验证点是否在预选通道内
|
||
if (_selectedChannels != null && _selectedChannels.Count > 0)
|
||
{
|
||
LogManager.WriteLog($"[智能检测] 使用预选通道模式,通道数量: {_selectedChannels.Count}");
|
||
if (IsPointInSelectedChannels(worldPoint))
|
||
{
|
||
return new ChannelDetectionResult
|
||
{
|
||
IsValidLocation = true,
|
||
Message = "点击位置在预选通道内,可设置路径点",
|
||
DetectionMethod = "预选通道验证"
|
||
};
|
||
}
|
||
else
|
||
{
|
||
return new ChannelDetectionResult
|
||
{
|
||
IsValidLocation = false,
|
||
Message = "点击位置不在预选通道内,请在高亮的通道区域内点击",
|
||
DetectionMethod = "预选通道验证"
|
||
};
|
||
}
|
||
}
|
||
|
||
// 2. 自动检测点击位置的物流属性
|
||
LogManager.WriteLog($"[智能检测] 启用自动检测模式");
|
||
var autoDetectionResult = AutoDetectLogisticsChannel(worldPoint);
|
||
|
||
if (autoDetectionResult.IsValidChannel)
|
||
{
|
||
LogManager.WriteLog($"[智能检测] 自动检测到有效物流通道: {autoDetectionResult.ChannelType}");
|
||
|
||
// 如果检测到有效通道,自动设为选中通道
|
||
if (autoDetectionResult.DetectedChannel != null)
|
||
{
|
||
_selectedChannels = new List<ModelItem> { autoDetectionResult.DetectedChannel };
|
||
HighlightLogisticsChannels();
|
||
LogManager.WriteLog($"[智能检测] 已自动选中检测到的通道");
|
||
}
|
||
|
||
return new ChannelDetectionResult
|
||
{
|
||
IsValidLocation = true,
|
||
Message = $"检测到{autoDetectionResult.ChannelType},可设置路径点",
|
||
DetectionMethod = "自动检测"
|
||
};
|
||
}
|
||
else
|
||
{
|
||
return new ChannelDetectionResult
|
||
{
|
||
IsValidLocation = false,
|
||
Message = autoDetectionResult.ErrorMessage ?? "点击位置不是物流通道,请在通道、走廊等可通行区域点击",
|
||
DetectionMethod = "自动检测"
|
||
};
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[智能检测] 智能检测异常: {ex.Message}");
|
||
return new ChannelDetectionResult
|
||
{
|
||
IsValidLocation = false,
|
||
Message = "智能检测失败,请手动选择通道后再设置路径点",
|
||
DetectionMethod = "异常处理"
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动检测物流通道
|
||
/// </summary>
|
||
/// <param name="worldPoint">检测点</param>
|
||
/// <returns>检测结果</returns>
|
||
private LogisticsChannelDetectionResult AutoDetectLogisticsChannel(Point3D worldPoint)
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog($"[自动检测] 开始分析点击位置的物流属性");
|
||
|
||
// 获取点击位置附近的所有模型项
|
||
var nearbyItems = GetNearbyModelItems(worldPoint, 2.0); // 2米范围内
|
||
LogManager.WriteLog($"[自动检测] 找到附近模型项数量: {nearbyItems.Count}");
|
||
|
||
foreach (var item in nearbyItems)
|
||
{
|
||
// 检查是否有物流属性标记
|
||
if (CategoryAttributeManager.HasLogisticsAttributes(item))
|
||
{
|
||
var typeValue = CategoryAttributeManager.GetLogisticsPropertyValue(item, CategoryAttributeManager.LogisticsProperties.TYPE);
|
||
LogManager.WriteLog($"[自动检测] 发现物流属性类型: {typeValue}");
|
||
|
||
if (!string.IsNullOrEmpty(typeValue))
|
||
{
|
||
// 尝试解析物流类型
|
||
if (Enum.TryParse<CategoryAttributeManager.LogisticsElementType>(typeValue, out var logisticsCategory))
|
||
{
|
||
if (IsLogisticsChannelType(logisticsCategory))
|
||
{
|
||
return new LogisticsChannelDetectionResult
|
||
{
|
||
IsValidChannel = true,
|
||
ChannelType = GetChannelTypeName(logisticsCategory),
|
||
DetectedChannel = item,
|
||
LogisticsCategory = logisticsCategory
|
||
};
|
||
}
|
||
else
|
||
{
|
||
return new LogisticsChannelDetectionResult
|
||
{
|
||
IsValidChannel = false,
|
||
ErrorMessage = $"检测到{GetChannelTypeName(logisticsCategory)},但此类型不可作为路径点",
|
||
LogisticsCategory = logisticsCategory
|
||
};
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 如果不能解析为枚举,基于字符串进行简单判断
|
||
var isChannel = IsChannelTypeByName(typeValue);
|
||
return new LogisticsChannelDetectionResult
|
||
{
|
||
IsValidChannel = isChannel,
|
||
ChannelType = typeValue,
|
||
DetectedChannel = item,
|
||
ErrorMessage = isChannel ? null : $"检测到{typeValue},但此类型不可作为路径点"
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有明确的物流属性,尝试基于几何特征判断
|
||
var geometryAnalysisResult = AnalyzeGeometryForChannelLikelihood(nearbyItems, worldPoint);
|
||
if (geometryAnalysisResult.IsLikelyChannel)
|
||
{
|
||
LogManager.WriteLog($"[自动检测] 几何分析显示可能是通道: {geometryAnalysisResult.Reason}");
|
||
return new LogisticsChannelDetectionResult
|
||
{
|
||
IsValidChannel = true,
|
||
ChannelType = "推测通道",
|
||
DetectedChannel = geometryAnalysisResult.MostLikelyChannel,
|
||
DetectionConfidence = geometryAnalysisResult.Confidence
|
||
};
|
||
}
|
||
|
||
LogManager.WriteLog($"[自动检测] 未检测到有效的物流通道属性");
|
||
return new LogisticsChannelDetectionResult
|
||
{
|
||
IsValidChannel = false,
|
||
ErrorMessage = "此位置未识别为物流通道"
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[自动检测] 自动检测异常: {ex.Message}");
|
||
return new LogisticsChannelDetectionResult
|
||
{
|
||
IsValidChannel = false,
|
||
ErrorMessage = $"自动检测失败: {ex.Message}"
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取附近的模型项
|
||
/// </summary>
|
||
/// <param name="worldPoint">检测点</param>
|
||
/// <param name="radius">搜索半径(米)</param>
|
||
/// <returns>附近的模型项列表</returns>
|
||
private List<ModelItem> GetNearbyModelItems(Point3D worldPoint, double radius)
|
||
{
|
||
var nearbyItems = new List<ModelItem>();
|
||
try
|
||
{
|
||
var allItems = Application.ActiveDocument.Models.First.RootItem.DescendantsAndSelf;
|
||
|
||
foreach (var item in allItems)
|
||
{
|
||
var boundingBox = item.BoundingBox();
|
||
if (boundingBox != null)
|
||
{
|
||
// 检查点是否在扩展的包围盒内
|
||
var expandedMin = new Point3D(
|
||
boundingBox.Min.X - radius,
|
||
boundingBox.Min.Y - radius,
|
||
boundingBox.Min.Z - radius);
|
||
var expandedMax = new Point3D(
|
||
boundingBox.Max.X + radius,
|
||
boundingBox.Max.Y + radius,
|
||
boundingBox.Max.Z + radius);
|
||
|
||
if (worldPoint.X >= expandedMin.X && worldPoint.X <= expandedMax.X &&
|
||
worldPoint.Y >= expandedMin.Y && worldPoint.Y <= expandedMax.Y &&
|
||
worldPoint.Z >= expandedMin.Z && worldPoint.Z <= expandedMax.Z)
|
||
{
|
||
nearbyItems.Add(item);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[附近项搜索] 异常: {ex.Message}");
|
||
}
|
||
|
||
return nearbyItems;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断物流类别是否为通道类型
|
||
/// </summary>
|
||
/// <param name="category">物流类别</param>
|
||
/// <returns>是否为通道类型</returns>
|
||
private bool IsLogisticsChannelType(CategoryAttributeManager.LogisticsElementType category)
|
||
{
|
||
switch (category)
|
||
{
|
||
case CategoryAttributeManager.LogisticsElementType.通道:
|
||
case CategoryAttributeManager.LogisticsElementType.门:
|
||
case CategoryAttributeManager.LogisticsElementType.电梯:
|
||
case CategoryAttributeManager.LogisticsElementType.楼梯:
|
||
case CategoryAttributeManager.LogisticsElementType.装卸区:
|
||
return true;
|
||
case CategoryAttributeManager.LogisticsElementType.障碍物:
|
||
return false;
|
||
default:
|
||
return true; // 默认其他类型可通行
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取通道类型名称
|
||
/// </summary>
|
||
/// <param name="category">物流类别</param>
|
||
/// <returns>类型名称</returns>
|
||
private string GetChannelTypeName(CategoryAttributeManager.LogisticsElementType category)
|
||
{
|
||
switch (category)
|
||
{
|
||
case CategoryAttributeManager.LogisticsElementType.通道:
|
||
return "通道";
|
||
case CategoryAttributeManager.LogisticsElementType.门:
|
||
return "门";
|
||
case CategoryAttributeManager.LogisticsElementType.电梯:
|
||
return "电梯";
|
||
case CategoryAttributeManager.LogisticsElementType.楼梯:
|
||
return "楼梯";
|
||
case CategoryAttributeManager.LogisticsElementType.装卸区:
|
||
return "装卸区";
|
||
case CategoryAttributeManager.LogisticsElementType.障碍物:
|
||
return "障碍物";
|
||
case CategoryAttributeManager.LogisticsElementType.停车位:
|
||
return "停车位";
|
||
case CategoryAttributeManager.LogisticsElementType.检查点:
|
||
return "检查点";
|
||
default:
|
||
return "未知类型";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 基于名称判断是否为通道类型
|
||
/// </summary>
|
||
/// <param name="typeName">类型名称</param>
|
||
/// <returns>是否为通道类型</returns>
|
||
private bool IsChannelTypeByName(string typeName)
|
||
{
|
||
if (string.IsNullOrEmpty(typeName))
|
||
return false;
|
||
|
||
// 通道相关的关键词
|
||
var channelKeywords = new[] { "通道", "门", "电梯", "楼梯", "装卸", "走廊", "过道", "通路" };
|
||
var obstacleKeywords = new[] { "障碍物", "墙", "柱子", "设备" };
|
||
|
||
var typeLower = typeName.ToLower();
|
||
|
||
// 如果包含障碍物关键词,返回false
|
||
if (obstacleKeywords.Any(keyword => typeLower.Contains(keyword.ToLower())))
|
||
return false;
|
||
|
||
// 如果包含通道关键词,返回true
|
||
if (channelKeywords.Any(keyword => typeLower.Contains(keyword.ToLower())))
|
||
return true;
|
||
|
||
// 默认允许通过(宽容处理)
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 基于几何特征分析通道可能性
|
||
/// </summary>
|
||
/// <param name="items">模型项列表</param>
|
||
/// <param name="worldPoint">检测点</param>
|
||
/// <returns>几何分析结果</returns>
|
||
private GeometryAnalysisResult AnalyzeGeometryForChannelLikelihood(List<ModelItem> items, Point3D worldPoint)
|
||
{
|
||
try
|
||
{
|
||
// 简化实现:如果有模型项且点在其包围盒内,则认为可能是通道
|
||
foreach (var item in items)
|
||
{
|
||
var boundingBox = item.BoundingBox();
|
||
if (boundingBox != null)
|
||
{
|
||
if (IsPointInBoundingBox(worldPoint, boundingBox))
|
||
{
|
||
// 基于包围盒尺寸判断是否像通道
|
||
var width = boundingBox.Max.X - boundingBox.Min.X;
|
||
var height = boundingBox.Max.Y - boundingBox.Min.Y;
|
||
var depth = boundingBox.Max.Z - boundingBox.Min.Z;
|
||
|
||
// 简单的启发式规则:如果有一个维度明显较大,可能是通道
|
||
var maxDimension = Math.Max(Math.Max(width, height), depth);
|
||
var minDimension = Math.Min(Math.Min(width, height), depth);
|
||
|
||
if (maxDimension > minDimension * 3) // 长宽比大于3:1
|
||
{
|
||
return new GeometryAnalysisResult
|
||
{
|
||
IsLikelyChannel = true,
|
||
Reason = "检测到细长形状的模型,可能是通道",
|
||
MostLikelyChannel = item,
|
||
Confidence = 0.7
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return new GeometryAnalysisResult
|
||
{
|
||
IsLikelyChannel = false,
|
||
Reason = "几何特征不符合通道特征",
|
||
Confidence = 0.2
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[几何分析] 异常: {ex.Message}");
|
||
return new GeometryAnalysisResult
|
||
{
|
||
IsLikelyChannel = false,
|
||
Reason = $"几何分析失败: {ex.Message}",
|
||
Confidence = 0.0
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <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("[智能类型] 开始智能点类型切换");
|
||
|
||
if (_currentRoute == null || _currentRoute.Points == null)
|
||
{
|
||
_currentPointType = PathPointType.StartPoint;
|
||
LogManager.WriteLog("[智能类型] 无路径或无点,设置为起点类型");
|
||
return;
|
||
}
|
||
|
||
var pointCount = _currentRoute.Points.Count;
|
||
var hasStartPoint = _currentRoute.Points.Any(p => p.Type == PathPointType.StartPoint);
|
||
var hasEndPoint = _currentRoute.Points.Any(p => p.Type == PathPointType.EndPoint);
|
||
|
||
LogManager.WriteLog($"[智能类型] 路径状态分析:点数={pointCount}, 有起点={hasStartPoint}, 有终点={hasEndPoint}");
|
||
|
||
if (pointCount == 0 || !hasStartPoint)
|
||
{
|
||
// 没有点或没有起点时,下一个点应该是起点
|
||
_currentPointType = PathPointType.StartPoint;
|
||
LogManager.WriteLog("[智能类型] 自动设置为起点(首个点或缺失起点)");
|
||
}
|
||
else if (hasStartPoint && !hasEndPoint && pointCount >= 2)
|
||
{
|
||
// 已有起点,没有终点,且已有至少2个点(起点+1个路径点),可以选择添加终点
|
||
// 但默认仍为路径点,用户可手动选择终点
|
||
_currentPointType = PathPointType.WayPoint;
|
||
LogManager.WriteLog("[智能类型] 自动设置为路径点(建议手动设置终点)");
|
||
}
|
||
else if (hasStartPoint && !hasEndPoint)
|
||
{
|
||
// 已有起点,没有终点,点数少于2个,继续添加路径点
|
||
_currentPointType = PathPointType.WayPoint;
|
||
LogManager.WriteLog("[智能类型] 自动设置为路径点(构建中间路径)");
|
||
}
|
||
else
|
||
{
|
||
// 其他情况默认为路径点
|
||
_currentPointType = PathPointType.WayPoint;
|
||
LogManager.WriteLog("[智能类型] 默认设置为路径点");
|
||
}
|
||
|
||
// 触发类型变更事件
|
||
OnCurrentPointTypeChanged();
|
||
|
||
LogManager.WriteLog("[智能类型] 智能点类型切换完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[智能类型] 智能切换异常: {ex.Message}");
|
||
LogManager.WriteLog($"[智能类型] 异常堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 智能确定终点
|
||
/// 当用户明确想要结束路径时调用
|
||
/// </summary>
|
||
public void SetNextPointAsEndPoint()
|
||
{
|
||
if (_currentRoute != null && _currentRoute.Points.Count > 0)
|
||
{
|
||
var hasEndPoint = _currentRoute.Points.Any(p => p.Type == PathPointType.EndPoint);
|
||
if (!hasEndPoint)
|
||
{
|
||
_currentPointType = PathPointType.EndPoint;
|
||
LogManager.WriteLog("[智能类型] 用户设置下一个点为终点");
|
||
OnCurrentPointTypeChanged();
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[智能类型] 路径已有终点,无法再设置终点");
|
||
OnErrorOccurred("当前路径已有终点,无法添加新的终点");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[智能类型] 没有路径或路径为空,无法设置终点");
|
||
OnErrorOccurred("请先添加起点和路径点,再设置终点");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动调整已有路径点的类型
|
||
/// 确保路径逻辑正确:第一个点为起点,最后一个点为终点
|
||
/// </summary>
|
||
public void AutoAdjustPathPointTypes()
|
||
{
|
||
if (_currentRoute == null || _currentRoute.Points == null || _currentRoute.Points.Count == 0)
|
||
{
|
||
LogManager.WriteLog("[类型调整] 无路径点需要调整");
|
||
return;
|
||
}
|
||
|
||
var points = _currentRoute.Points;
|
||
var adjustmentsMade = false;
|
||
|
||
LogManager.WriteLog($"[类型调整] 开始调整路径点类型,总点数: {points.Count}");
|
||
|
||
// 确保第一个点是起点
|
||
if (points.Count > 0 && points[0].Type != PathPointType.StartPoint)
|
||
{
|
||
var oldType = points[0].Type;
|
||
points[0].Type = PathPointType.StartPoint;
|
||
points[0].Name = GeneratePointName(PathPointType.StartPoint);
|
||
adjustmentsMade = true;
|
||
LogManager.WriteLog($"[类型调整] 第1个点从{oldType}调整为起点: {points[0].Name}");
|
||
}
|
||
|
||
// 确保最后一个点是终点(如果路径已完整)
|
||
if (points.Count > 1)
|
||
{
|
||
var lastPoint = points[points.Count - 1];
|
||
if (lastPoint.Type != PathPointType.EndPoint)
|
||
{
|
||
var oldType = lastPoint.Type;
|
||
lastPoint.Type = PathPointType.EndPoint;
|
||
lastPoint.Name = GeneratePointName(PathPointType.EndPoint);
|
||
adjustmentsMade = true;
|
||
LogManager.WriteLog($"[类型调整] 最后一个点从{oldType}调整为终点: {lastPoint.Name}");
|
||
}
|
||
}
|
||
|
||
// 确保中间的点都是路径点
|
||
for (int i = 1; i < points.Count - 1; i++)
|
||
{
|
||
if (points[i].Type != PathPointType.WayPoint)
|
||
{
|
||
var oldType = points[i].Type;
|
||
points[i].Type = PathPointType.WayPoint;
|
||
points[i].Name = GeneratePointName(PathPointType.WayPoint);
|
||
adjustmentsMade = true;
|
||
LogManager.WriteLog($"[类型调整] 第{i + 1}个点从{oldType}调整为路径点: {points[i].Name}");
|
||
}
|
||
}
|
||
|
||
if (adjustmentsMade)
|
||
{
|
||
LogManager.WriteLog("[类型调整] 路径点类型调整完成");
|
||
OnStatusChanged("已自动调整路径点类型:起点→路径点→终点");
|
||
|
||
// 触发路径更新事件
|
||
CurrentRouteChanged?.Invoke(this, _currentRoute);
|
||
PathPointAddedIn3D?.Invoke(this, null); // 触发UI更新
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[类型调整] 路径点类型已正确,无需调整");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发当前点类型变更事件
|
||
/// </summary>
|
||
private void OnCurrentPointTypeChanged()
|
||
{
|
||
try
|
||
{
|
||
// 这里可以触发UI更新,比如更新按钮状态、标签文本等
|
||
LogManager.WriteLog($"[事件] 当前点类型已变更为: {_currentPointType}");
|
||
OnStatusChanged($"当前点类型: {GetPointTypeName(_currentPointType)}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[事件] 触发点类型变更事件时异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从3D视图和当前路径中删除路径点
|
||
/// </summary>
|
||
/// <param name="pathPoint">要删除的路径点</param>
|
||
/// <returns>是否成功删除</returns>
|
||
public bool RemovePathPointFrom3D(PathPoint pathPoint)
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog($"[路径点删除] 开始删除路径点: {pathPoint?.Name}");
|
||
|
||
if (pathPoint == null)
|
||
{
|
||
LogManager.WriteLog("[路径点删除] 路径点为空,无法删除");
|
||
OnErrorOccurred("无法删除空的路径点");
|
||
return false;
|
||
}
|
||
|
||
if (_currentRoute == null)
|
||
{
|
||
LogManager.WriteLog("[路径点删除] 当前路径为空,无法删除路径点");
|
||
OnErrorOccurred("当前没有活动路径");
|
||
return false;
|
||
}
|
||
|
||
// 从当前路径中删除点
|
||
var removed = _currentRoute.RemovePoint(pathPoint);
|
||
if (!removed)
|
||
{
|
||
LogManager.WriteLog($"[路径点删除] 路径点不存在于当前路径中: {pathPoint.Name}");
|
||
OnErrorOccurred("路径点不存在于当前路径中");
|
||
return false;
|
||
}
|
||
|
||
// 清除3D标记
|
||
Clear3DPathPointMarker(pathPoint);
|
||
|
||
// 重新绘制剩余的路径点(更新序号)
|
||
RedrawAllPathPoints();
|
||
|
||
// 触发删除事件
|
||
PathPointRemovedFrom3D?.Invoke(this, pathPoint);
|
||
|
||
// 触发路径更新事件
|
||
PathPointsListUpdated?.Invoke(this, _currentRoute);
|
||
|
||
// 添加历史记录(仅在编辑状态下)
|
||
if (IsInEditableState && _currentRoute != null)
|
||
{
|
||
var historyEntry = new PathHistoryEntry(
|
||
_currentRoute.Id,
|
||
PathHistoryOperationType.PointRemoved,
|
||
_currentRoute,
|
||
$"删除路径点: {pathPoint.Name}");
|
||
_historyManager.AddHistoryEntry(historyEntry);
|
||
}
|
||
|
||
LogManager.WriteLog($"[路径点删除] 成功删除路径点: {pathPoint.Name}");
|
||
OnStatusChanged($"已删除路径点: {pathPoint.Name}");
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[路径点删除] 删除路径点异常: {ex.Message}");
|
||
LogManager.WriteLog($"[路径点删除] 异常堆栈: {ex.StackTrace}");
|
||
OnErrorOccurred($"删除路径点失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除指定路径点的3D标记
|
||
/// </summary>
|
||
/// <param name="pathPoint">要清除标记的路径点</param>
|
||
private void Clear3DPathPointMarker(PathPoint pathPoint)
|
||
{
|
||
try
|
||
{
|
||
if (_pathPointMarkers != null)
|
||
{
|
||
// 移除相关的标记
|
||
_pathPointMarkers.RemoveAll(marker => marker.PathPoint?.Id == pathPoint.Id);
|
||
LogManager.WriteLog($"[标记清除] 已清除路径点的3D标记: {pathPoint.Name}");
|
||
}
|
||
|
||
// 使用RenderPlugin清除圆形标记
|
||
if (_renderPlugin != null)
|
||
{
|
||
var removed = _renderPlugin.RemoveMarkerAt(pathPoint.Position, 1.0);
|
||
LogManager.WriteLog($"[标记清除] 圆形标记清除结果: {removed}, 路径点: {pathPoint.Name}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[标记清除] 清除3D标记异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重新绘制所有路径点(更新序号和标记)
|
||
/// </summary>
|
||
private void RedrawAllPathPoints()
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog("[重绘路径] 开始重新绘制所有路径点");
|
||
|
||
if (_currentRoute?.Points == null || _currentRoute.Points.Count == 0)
|
||
{
|
||
LogManager.WriteLog("[重绘路径] 当前路径无点,无需重绘");
|
||
return;
|
||
}
|
||
|
||
// 先清除所有现有标记
|
||
Clear3DPathMarkers();
|
||
|
||
// 重新绘制每个点
|
||
foreach (var point in _currentRoute.Points)
|
||
{
|
||
Draw3DPathPoint(point);
|
||
}
|
||
|
||
LogManager.WriteLog($"[重绘路径] 重新绘制完成,共{_currentRoute.Points.Count}个路径点");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[重绘路径] 重绘路径点异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定位置附近的路径点
|
||
/// </summary>
|
||
/// <param name="position">3D位置</param>
|
||
/// <param name="tolerance">容差范围(米)</param>
|
||
/// <returns>附近的路径点,如果没有返回null</returns>
|
||
public PathPoint GetNearbyPathPoint(Point3D position, double tolerance = 1.0)
|
||
{
|
||
try
|
||
{
|
||
if (_currentRoute?.Points == null || _currentRoute.Points.Count == 0)
|
||
return null;
|
||
|
||
PathPoint nearestPoint = null;
|
||
double minDistance = double.MaxValue;
|
||
|
||
foreach (var point in _currentRoute.Points)
|
||
{
|
||
var distance = CalculateDistance3D(position, point.Position);
|
||
if (distance <= tolerance && distance < minDistance)
|
||
{
|
||
minDistance = distance;
|
||
nearestPoint = point;
|
||
}
|
||
}
|
||
|
||
if (nearestPoint != null)
|
||
{
|
||
LogManager.WriteLog($"[附近搜索] 找到附近路径点: {nearestPoint.Name}, 距离: {minDistance:F2}米");
|
||
}
|
||
|
||
return nearestPoint;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[附近搜索] 搜索附近路径点异常: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 实时同步路径点列表
|
||
/// 当路径发生变化时,通知UI更新
|
||
/// </summary>
|
||
public void SyncPathPointsList()
|
||
{
|
||
try
|
||
{
|
||
LogManager.WriteLog("[实时同步] 开始同步路径点列表");
|
||
|
||
if (_currentRoute != null)
|
||
{
|
||
// 重新计算路径长度
|
||
_currentRoute.RecalculateLength();
|
||
|
||
// 触发路径更新事件
|
||
PathPointsListUpdated?.Invoke(this, _currentRoute);
|
||
CurrentRouteChanged?.Invoke(this, _currentRoute);
|
||
|
||
LogManager.WriteLog($"[实时同步] 路径同步完成,当前点数: {_currentRoute.Points.Count}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.WriteLog("[实时同步] 当前路径为空,触发空路径同步");
|
||
PathPointsListUpdated?.Invoke(this, null);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"[实时同步] 同步路径点列表异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选择路径点(高亮显示)
|
||
/// </summary>
|
||
/// <param name="pathPoint">要选择的路径点</param>
|
||
public void SelectPathPoint(PathPoint pathPoint)
|
||
{
|
||
try
|
||
{
|
||
if (pathPoint == null)
|
||
{
|
||
LogManager.WriteLog("[路径点选择] 取消路径点选择");
|
||
return;
|
||
}
|
||
|
||
LogManager.WriteLog($"[路径点选择] 选择路径点: {pathPoint.Name}");
|
||
|
||
// 高亮显示选中的路径点
|
||
HighlightNearbyItems(pathPoint.Position, new Color(1.0f, 1.0f, 0.0f)); // 黄色
|
||
|
||
// 更新状态信息
|
||
OnStatusChanged($"已选择路径点: {pathPoint.Name} ({GetPointTypeName(pathPoint.Type)})");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.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
|
||
|
||
#region ToolPlugin 集成
|
||
|
||
/// <summary>
|
||
/// ToolPlugin是否已激活
|
||
/// </summary>
|
||
private bool _isToolPluginActive = false;
|
||
|
||
/// <summary>
|
||
/// 智能ToolPlugin管理:根据编辑状态自动激活或停用
|
||
/// </summary>
|
||
private void ManageToolPluginForEditState()
|
||
{
|
||
if (IsInEditableState)
|
||
{
|
||
// 编辑状态:确保ToolPlugin已激活
|
||
if (!_isToolPluginActive)
|
||
{
|
||
ActivateToolPlugin();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 查看状态:确保ToolPlugin已停用
|
||
if (_isToolPluginActive)
|
||
{
|
||
DeactivateToolPlugin();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 激活自定义ToolPlugin进行精确点击检测
|
||
/// </summary>
|
||
private bool ActivateToolPlugin()
|
||
{
|
||
// 如果已经激活,直接返回成功
|
||
if (_isToolPluginActive)
|
||
{
|
||
LogManager.WriteLog("[ToolPlugin] ToolPlugin已激活,无需重复激活");
|
||
return true;
|
||
}
|
||
|
||
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)
|
||
{
|
||
// 不再传递点类型,让AddPathPointIn3D方法内部自动判断
|
||
LogManager.WriteLog($"[ToolPlugin事件] 调用AddPathPointIn3D添加路径点");
|
||
var pathPoint = AddPathPointIn3D(pickResult.Point);
|
||
|
||
if (pathPoint != null)
|
||
{
|
||
LogManager.WriteLog($"[ToolPlugin事件] ✓ 路径点添加成功: {pathPoint.Name}");
|
||
}
|
||
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;
|
||
|
||
#region 新的路径编辑状态管理方法
|
||
|
||
/// <summary>
|
||
/// 开始新建路径
|
||
/// </summary>
|
||
/// <param name="routeName">路径名称</param>
|
||
/// <returns>创建的新路径</returns>
|
||
public PathRoute StartCreatingNewRoute(string routeName = null)
|
||
{
|
||
try
|
||
{
|
||
// 清除旧的3D标记,避免连接到上一条路径
|
||
if (_renderPlugin != null)
|
||
{
|
||
_renderPlugin.ClearAllMarkers();
|
||
LogManager.Info("已清除旧的3D路径标记,准备创建新路径");
|
||
}
|
||
|
||
// 设置为创建状态
|
||
PathEditState = PathEditState.Creating;
|
||
|
||
// 创建新路径
|
||
var newRoute = new PathRoute(routeName ?? $"路径{DateTime.Now:yyyyMMdd_HHmmss}");
|
||
_editingRoute = newRoute;
|
||
CurrentRoute = newRoute;
|
||
|
||
// 自动选择所有物流通道
|
||
AutoSelectLogisticsChannels();
|
||
|
||
// 智能管理ToolPlugin状态
|
||
ManageToolPluginForEditState();
|
||
|
||
// 高亮通道以便用户点击
|
||
HighlightLogisticsChannels();
|
||
|
||
LogManager.Info($"开始新建路径: {newRoute.Name}");
|
||
OnStatusChanged($"正在新建路径: {newRoute.Name} - 请在3D视图中点击设置路径点");
|
||
|
||
return newRoute;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
OnErrorOccurred($"开始新建路径失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动选择所有物流通道
|
||
/// </summary>
|
||
private void AutoSelectLogisticsChannels()
|
||
{
|
||
try
|
||
{
|
||
var document = Application.ActiveDocument;
|
||
if (document?.Models == null) return;
|
||
|
||
_selectedChannels.Clear();
|
||
|
||
// 收集所有物流标记的模型
|
||
foreach (ModelItem rootItem in document.Models.RootItems)
|
||
{
|
||
foreach (ModelItem item in rootItem.DescendantsAndSelf)
|
||
{
|
||
if (CategoryAttributeManager.HasLogisticsAttributes(item))
|
||
{
|
||
_selectedChannels.Add(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (_selectedChannels.Count > 0)
|
||
{
|
||
// 计算组合边界
|
||
CalculateCombinedBounds();
|
||
|
||
// 触发事件
|
||
ChannelsSelected?.Invoke(this, _selectedChannels);
|
||
|
||
LogManager.Info($"自动选择了 {_selectedChannels.Count} 个物流通道");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("未找到任何物流通道");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"自动选择物流通道失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 高亮所有物流通道
|
||
/// </summary>
|
||
private void HighlightLogisticsChannels()
|
||
{
|
||
try
|
||
{
|
||
var document = Application.ActiveDocument;
|
||
if (document?.Models == null) return;
|
||
|
||
var logisticsChannels = new ModelItemCollection();
|
||
|
||
// 收集所有物流标记的模型
|
||
foreach (ModelItem rootItem in document.Models.RootItems)
|
||
{
|
||
foreach (ModelItem item in rootItem.DescendantsAndSelf)
|
||
{
|
||
if (CategoryAttributeManager.HasLogisticsAttributes(item))
|
||
{
|
||
logisticsChannels.Add(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 高亮显示
|
||
if (logisticsChannels.Count > 0)
|
||
{
|
||
// 改为淡青色,避免与黄色连线冲突
|
||
HighlightChannels(logisticsChannels, System.Drawing.Color.Cyan);
|
||
LogManager.Info($"高亮显示 {logisticsChannels.Count} 个物流通道为青色");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.WriteLog($"高亮物流通道失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 路径历史记录管理器(只读访问)
|
||
/// </summary>
|
||
public PathHistoryManager HistoryManager => _historyManager;
|
||
|
||
/// <summary>
|
||
/// 历史记录变更事件
|
||
/// </summary>
|
||
public event EventHandler<PathHistoryEntry> HistoryEntryAdded
|
||
{
|
||
add { _historyManager.HistoryEntryAdded += value; }
|
||
remove { _historyManager.HistoryEntryAdded -= value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新3D路径点标记的外观
|
||
/// </summary>
|
||
private void UpdateMarkerAppearance(PathPoint point)
|
||
{
|
||
if (_renderPlugin == null) return;
|
||
|
||
// 获取点的序号(从1开始)
|
||
int sequenceNumber = GetPathPointNumber(point);
|
||
if (sequenceNumber == -1) return;
|
||
|
||
// 获取新类型对应的颜色和半径
|
||
var newColor = _renderPlugin.GetColorForPointType(point.Type);
|
||
var newRadius = _renderPlugin.GetRadiusForPointType(point.Type);
|
||
|
||
// 调用渲染插件来更新标记
|
||
_renderPlugin.UpdateMarker(sequenceNumber, newColor, newRadius);
|
||
}
|
||
}
|
||
} |