3570 lines
139 KiB
C#
3570 lines
139 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Autodesk.Navisworks.Api;
|
||
using Autodesk.Navisworks.Api.Plugins;
|
||
using NavisworksTransport.Core.Config;
|
||
using NavisworksTransport.Utils;
|
||
using NavisworksTransport.Utils.CoordinateSystem;
|
||
using static NavisworksTransport.Core.Config.ConfigManager;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// 路径可视化模式枚举
|
||
/// </summary>
|
||
public enum PathVisualizationMode
|
||
{
|
||
/// <summary>
|
||
/// 标准连线模式 - 圆柱形连线
|
||
/// </summary>
|
||
StandardLine,
|
||
|
||
/// <summary>
|
||
/// 带状连线模式 - 长方体连线
|
||
/// </summary>
|
||
RibbonLine,
|
||
|
||
/// <summary>
|
||
/// 物体通行空间模式 - 矩形通道
|
||
/// </summary>
|
||
ObjectSpace
|
||
}
|
||
|
||
/// <summary>
|
||
/// 网格点类型枚举
|
||
/// </summary>
|
||
public enum GridPointType
|
||
{
|
||
/// <summary>
|
||
/// 球形
|
||
/// </summary>
|
||
Sphere,
|
||
|
||
/// <summary>
|
||
/// 正方形
|
||
/// </summary>
|
||
Rectangle,
|
||
|
||
/// <summary>
|
||
/// 圆
|
||
/// </summary>
|
||
Circle
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 可视化元素样式名称枚举
|
||
/// </summary>
|
||
public enum RenderStyleName
|
||
{
|
||
/// <summary>
|
||
/// 路径起点样式(绿色)
|
||
/// </summary>
|
||
StartPoint,
|
||
|
||
/// <summary>
|
||
/// 路径终点样式(红色)
|
||
/// </summary>
|
||
EndPoint,
|
||
|
||
/// <summary>
|
||
/// 路径中间点样式(蓝色)
|
||
/// </summary>
|
||
WayPoint,
|
||
|
||
/// <summary>
|
||
/// 连线样式(橙色)
|
||
/// </summary>
|
||
Line,
|
||
|
||
/// <summary>
|
||
/// 物体通行空间样式(灰色)
|
||
/// </summary>
|
||
ObjectSpace,
|
||
|
||
/// <summary>
|
||
/// 预览点样式(白色)
|
||
/// </summary>
|
||
PreviewPoint,
|
||
|
||
/// <summary>
|
||
/// 预览连线样式(灰色)
|
||
/// </summary>
|
||
PreviewLine,
|
||
|
||
/// <summary>
|
||
/// 未到达终点样式(深红色)
|
||
/// </summary>
|
||
UnreachedEndPoint,
|
||
|
||
/// <summary>
|
||
/// 网格通道样式(浅绿色)
|
||
/// </summary>
|
||
GridChannel,
|
||
|
||
/// <summary>
|
||
/// 网格障碍物样式(灰色)
|
||
/// </summary>
|
||
GridObstacle,
|
||
|
||
/// <summary>
|
||
/// 网格未知区域样式(深橙色)
|
||
/// </summary>
|
||
GridUnknown,
|
||
|
||
/// <summary>
|
||
/// 切点样式(深红色)
|
||
/// </summary>
|
||
TangentPoint,
|
||
|
||
/// <summary>
|
||
/// 安全警告样式(红色)
|
||
/// </summary>
|
||
SafetyWarning,
|
||
|
||
/// <summary>
|
||
/// 空轨基准路径样式(浅红色)
|
||
/// </summary>
|
||
RailBaseline,
|
||
|
||
/// <summary>
|
||
/// 直线装配基准向量样式(绿色)
|
||
/// </summary>
|
||
AssemblyGuideLine,
|
||
|
||
/// <summary>
|
||
/// 直线装配安装参考面样式(绿色半透明)
|
||
/// </summary>
|
||
AssemblyInstallationPlane,
|
||
|
||
/// <summary>
|
||
/// 吊装路径样式(紫色)
|
||
/// </summary>
|
||
HoistingLine
|
||
,
|
||
|
||
/// <summary>
|
||
/// 真实物体原始位姿 X 轴样式(红色)
|
||
/// </summary>
|
||
TransformAxisX,
|
||
|
||
/// <summary>
|
||
/// 真实物体原始位姿 Y 轴样式(绿色)
|
||
/// </summary>
|
||
TransformAxisY,
|
||
|
||
/// <summary>
|
||
/// 真实物体原始位姿 Z 轴样式(蓝色)
|
||
/// </summary>
|
||
TransformAxisZ
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染样式,包含颜色和透明度
|
||
/// </summary>
|
||
public readonly struct RenderStyle
|
||
{
|
||
/// <summary>
|
||
/// 颜色
|
||
/// </summary>
|
||
public Color Color { get; }
|
||
|
||
/// <summary>
|
||
/// 透明度 (0.0 = 完全透明, 1.0 = 完全不透明)
|
||
/// </summary>
|
||
public double Alpha { get; }
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="color">颜色</param>
|
||
/// <param name="alpha">透明度</param>
|
||
public RenderStyle(Color color, double alpha)
|
||
{
|
||
Color = color;
|
||
Alpha = alpha;
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"RenderStyle[Color={Color}, Alpha={Alpha:F2}]";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 物体通行空间标记,用于渲染物体通道空间
|
||
/// </summary>
|
||
public class ObjectSpaceMarker
|
||
{
|
||
/// <summary>
|
||
/// 通道起点
|
||
/// </summary>
|
||
public Point3D StartPoint { get; set; }
|
||
|
||
/// <summary>
|
||
/// 通道终点
|
||
/// </summary>
|
||
public Point3D EndPoint { get; set; }
|
||
|
||
/// <summary>
|
||
/// 通道宽度(垂直于路径方向的尺寸)
|
||
/// </summary>
|
||
public double Width { get; set; }
|
||
|
||
/// <summary>
|
||
/// 通道高度(法线方向的尺寸)
|
||
/// </summary>
|
||
public double Height { get; set; }
|
||
|
||
/// <summary>
|
||
/// 通道颜色
|
||
/// </summary>
|
||
public Color Color { get; set; }
|
||
|
||
/// <summary>
|
||
/// 透明度(0.0-1.0,默认0.5即50%透明)
|
||
/// </summary>
|
||
public double Alpha { get; set; } = 0.5;
|
||
|
||
/// <summary>
|
||
/// 起点在路径中的索引
|
||
/// </summary>
|
||
public int FromIndex { get; set; }
|
||
|
||
/// <summary>
|
||
/// 终点在路径中的索引
|
||
/// </summary>
|
||
public int ToIndex { get; set; }
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"ObjectSpaceMarker[{FromIndex}->{ToIndex}, 宽度={Width:F2}m, 高度={Height:F2}m, 透明度={Alpha:F1}]";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连线标记,用于渲染路径点之间的连接线
|
||
/// </summary>
|
||
public class LineMarker
|
||
{
|
||
/// <summary>
|
||
/// 连线起点
|
||
/// </summary>
|
||
public Point3D StartPoint { get; set; }
|
||
|
||
/// <summary>
|
||
/// 连线终点
|
||
/// </summary>
|
||
public Point3D EndPoint { get; set; }
|
||
|
||
/// <summary>
|
||
/// 连线颜色
|
||
/// </summary>
|
||
public Color Color { get; set; }
|
||
|
||
/// <summary>
|
||
/// 连线半径
|
||
/// </summary>
|
||
public double Radius { get; set; }
|
||
|
||
/// <summary>
|
||
/// 起点在路径中的索引
|
||
/// </summary>
|
||
public int FromIndex { get; set; }
|
||
|
||
/// <summary>
|
||
/// 终点在路径中的索引
|
||
/// </summary>
|
||
public int ToIndex { get; set; }
|
||
|
||
/// <summary>
|
||
/// 路径段类型
|
||
/// </summary>
|
||
public PathSegmentType SegmentType { get; set; } = PathSegmentType.Straight;
|
||
|
||
/// <summary>
|
||
/// 圆弧轨迹数据(仅当SegmentType=Arc时有效)
|
||
/// </summary>
|
||
public ArcTrajectory Trajectory { get; set; }
|
||
|
||
/// <summary>
|
||
/// 采样点列表(用于圆弧段的多段圆柱体渲染)
|
||
/// </summary>
|
||
public List<Point3D> SampledPoints { get; set; }
|
||
|
||
/// <summary>
|
||
/// 带状连线高度(仅用于 RibbonLine 和 ObjectSpace 模式)
|
||
/// </summary>
|
||
public double Height { get; set; }
|
||
|
||
/// <summary>
|
||
/// 带状连线宽度(仅用于 ObjectSpace 模式)
|
||
/// </summary>
|
||
public double Width { get; set; }
|
||
|
||
/// <summary>
|
||
/// 透明度(0.0=完全透明,1.0=完全不透明)
|
||
/// </summary>
|
||
public double Opacity { get; set; } = 1.0;
|
||
|
||
/// <summary>
|
||
/// 水平段方向向量(用于垂直段渲染时确定通行空间方向)
|
||
/// </summary>
|
||
public Vector3D HorizontalDirection { get; set; }
|
||
|
||
/// <summary>
|
||
/// 通行空间的优先 up 方向(例如 Rail 安装面法向)
|
||
/// </summary>
|
||
public Vector3D UpDirection { get; set; }
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"LineMarker[{FromIndex}->{ToIndex}, 类型={SegmentType}, 起点=({StartPoint.X:F2},{StartPoint.Y:F2},{StartPoint.Z:F2}), 终点=({EndPoint.X:F2},{EndPoint.Y:F2},{EndPoint.Z:F2})]";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 路径可视化数据,包含一个完整路径的所有可视化元素
|
||
/// </summary>
|
||
public class PathVisualization
|
||
{
|
||
/// <summary>
|
||
/// 路径唯一标识
|
||
/// </summary>
|
||
public string PathId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 路径数据引用
|
||
/// </summary>
|
||
public PathRoute PathRoute { get; set; }
|
||
|
||
/// <summary>
|
||
/// 点标记集合
|
||
/// </summary>
|
||
public List<CircleMarker> PointMarkers { get; set; }
|
||
|
||
/// <summary>
|
||
/// 控制点连线集合(用户意图,半透明)
|
||
/// </summary>
|
||
public List<LineMarker> ControlLineMarkers { get; set; }
|
||
|
||
/// <summary>
|
||
/// 路径连线集合(真实路径,不透明)
|
||
/// </summary>
|
||
public List<LineMarker> PathLineMarkers { get; set; }
|
||
|
||
/// <summary>
|
||
/// 切点标记集合
|
||
/// </summary>
|
||
public List<SquareMarker> TangentMarkers { get; set; }
|
||
|
||
/// <summary>
|
||
/// 物体通行空间标记集合
|
||
/// </summary>
|
||
public List<ObjectSpaceMarker> ObjectSpaceMarkers { get; set; }
|
||
|
||
/// <summary>
|
||
/// 平面标记集合
|
||
/// </summary>
|
||
public List<PlaneMarker> PlaneMarkers { get; set; }
|
||
|
||
/// <summary>
|
||
/// 是否显示控制点可视化(用户意图)
|
||
/// </summary>
|
||
public bool ShowControlVisualization { get; set; } = true;
|
||
|
||
/// <summary>
|
||
/// 最后更新时间
|
||
/// </summary>
|
||
public DateTime LastUpdated { get; set; }
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
public PathVisualization()
|
||
{
|
||
PointMarkers = new List<CircleMarker>();
|
||
ControlLineMarkers = new List<LineMarker>();
|
||
PathLineMarkers = new List<LineMarker>();
|
||
TangentMarkers = new List<SquareMarker>();
|
||
ObjectSpaceMarkers = new List<ObjectSpaceMarker>();
|
||
PlaneMarkers = new List<PlaneMarker>();
|
||
LastUpdated = DateTime.Now;
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"PathVisualization[PathId={PathId}, 点数={PointMarkers.Count}, 控制连线={ControlLineMarkers.Count}, 路径连线={PathLineMarkers.Count}]";
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 路径点圆形标记渲染插件
|
||
/// 使用Graphics.Circle在3D空间绘制圆形标记
|
||
/// </summary>
|
||
[Plugin("PathPointRenderPlugin", "NavisworksTransport", DisplayName = "路径点圆形标记渲染器")]
|
||
public class PathPointRenderPlugin : RenderPlugin
|
||
{
|
||
private readonly object _lockObject = new object();
|
||
private Dictionary<string, PathVisualization> _pathVisualizations = new Dictionary<string, PathVisualization>();
|
||
private bool _isEnabled = true;
|
||
|
||
// 预览点标记
|
||
private CircleMarker _previewMarker = null;
|
||
|
||
// 预览连线标记
|
||
private List<LineMarker> _previewLines = new List<LineMarker>();
|
||
|
||
// 空轨基准路径可视化
|
||
private Dictionary<string, PathVisualization> _railBaselineVisualizations = new Dictionary<string, PathVisualization>();
|
||
|
||
// 当前网格大小(米),用于自适应点大小计算
|
||
private double _currentGridSizeInMeters;
|
||
|
||
// 路径可视化模式配置
|
||
private PathVisualizationMode _visualizationMode = PathVisualizationMode.RibbonLine;
|
||
|
||
// 独立可视化开关(替代单一模式)
|
||
private bool _showPathLines = true; // 显示路径线(地面连线/带状线)
|
||
private bool _showObjectSpace = false; // 显示通行空间
|
||
|
||
// 网格点类型配置
|
||
private GridPointType _gridPointType = GridPointType.Rectangle;
|
||
|
||
// 通行空间参数(最终尺寸,已包括安全间隙)- 内部使用模型单位
|
||
private double _passageAlongPath; // 通行空间沿路径方向的尺寸(模型单位)
|
||
private double _passageAcrossPath; // 通行空间垂直于路径方向的尺寸(模型单位)
|
||
private double _passageNormalToPath; // 通行空间法线方向的尺寸(模型单位)
|
||
|
||
// 物体尺寸 - 内部使用模型单位
|
||
private double _objectLength; // 物体长度(模型单位)
|
||
private double _objectHeight; // 物体高度(模型单位)
|
||
|
||
// 静态实例,用于外部访问
|
||
private static PathPointRenderPlugin _instance;
|
||
|
||
// 防抖机制:记录上次刷新时间(使用高精度计数器)
|
||
private long _lastRefreshTicks = 0;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
public PathPointRenderPlugin()
|
||
{
|
||
_instance = this;
|
||
|
||
// 订阅配置变更事件,实现通行空间透明度实时生效
|
||
try
|
||
{
|
||
ConfigManager.Instance.CategoryConfigurationChanged += OnConfigurationChanged;
|
||
LogManager.Info("[PathPointRenderPlugin] 已订阅配置变更事件");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[PathPointRenderPlugin] 订阅配置变更事件失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置变更事件处理
|
||
/// </summary>
|
||
private void OnConfigurationChanged(object sender, Tuple<ConfigCategory, ConfigurationChangedEventArgs> e)
|
||
{
|
||
try
|
||
{
|
||
// 只处理可视化配置变更
|
||
if (e.Item1 == ConfigCategory.Visualization || e.Item1 == ConfigCategory.All)
|
||
{
|
||
LogManager.Info("[PathPointRenderPlugin] 可视化配置已变更,刷新所有路径渲染");
|
||
|
||
// 重新构建所有路径可视化(使用新的透明度设置)
|
||
lock (_lockObject)
|
||
{
|
||
foreach (var visualization in _pathVisualizations.Values)
|
||
{
|
||
BuildVisualization(visualization);
|
||
}
|
||
}
|
||
|
||
// 请求视图刷新
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[PathPointRenderPlugin] 处理配置变更失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取静态实例
|
||
/// </summary>
|
||
public static PathPointRenderPlugin Instance => _instance;
|
||
|
||
/// <summary>
|
||
/// 路径可视化模式(向后兼容,根据 ShowPathLines 和 ShowObjectSpace 计算)
|
||
/// </summary>
|
||
public PathVisualizationMode VisualizationMode
|
||
{
|
||
get
|
||
{
|
||
// 根据新的独立开关计算模式
|
||
if (_showObjectSpace) return PathVisualizationMode.ObjectSpace;
|
||
if (_showPathLines) return PathVisualizationMode.RibbonLine;
|
||
return PathVisualizationMode.StandardLine;
|
||
}
|
||
set
|
||
{
|
||
_visualizationMode = value;
|
||
// 同步到新的独立开关
|
||
_showObjectSpace = (value == PathVisualizationMode.ObjectSpace);
|
||
_showPathLines = (value == PathVisualizationMode.RibbonLine || value == PathVisualizationMode.StandardLine);
|
||
// 模式改变时刷新所有普通路径(排除网格)
|
||
RefreshNormalPaths();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否显示路径线(地面连线/带状线)
|
||
/// </summary>
|
||
public bool ShowPathLines
|
||
{
|
||
get { return _showPathLines; }
|
||
set
|
||
{
|
||
_showPathLines = value;
|
||
RefreshNormalPaths();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否显示通行空间(物体通行空间)
|
||
/// </summary>
|
||
public bool ShowObjectSpace
|
||
{
|
||
get { return _showObjectSpace; }
|
||
set
|
||
{
|
||
_showObjectSpace = value;
|
||
RefreshNormalPaths();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 网格点类型
|
||
/// </summary>
|
||
public GridPointType GridPointType
|
||
{
|
||
get { return _gridPointType; }
|
||
set
|
||
{
|
||
_gridPointType = value;
|
||
// 类型改变时刷新所有网格路径
|
||
RefreshGridPaths();
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 是否启用渲染
|
||
/// </summary>
|
||
public bool IsEnabled
|
||
{
|
||
get { return _isEnabled; }
|
||
set
|
||
{
|
||
_isEnabled = value;
|
||
// 触发视图刷新
|
||
if (Application.ActiveDocument?.ActiveView != null)
|
||
{
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前路径总数
|
||
/// </summary>
|
||
public int PathCount
|
||
{
|
||
get
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
return _pathVisualizations.Count;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前点标记总数
|
||
/// </summary>
|
||
public int MarkerCount
|
||
{
|
||
get
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
return _pathVisualizations.Values.Sum(v => v.PointMarkers.Count);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Navisworks渲染回调方法
|
||
/// </summary>
|
||
/// <param name="view">当前视图</param>
|
||
/// <param name="graphics">图形上下文</param>
|
||
public override void Render(View view, Graphics graphics)
|
||
{
|
||
if (!_isEnabled)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 快速检查,避免频繁的API调用
|
||
var activeDoc = Application.ActiveDocument;
|
||
if (activeDoc?.Models == null || activeDoc.Models.Count == 0)
|
||
{
|
||
return; // 静默返回
|
||
}
|
||
|
||
// 检查是否有路径或预览点需要渲染
|
||
int pathCount;
|
||
int railBaselineCount;
|
||
bool hasPreviewPoint;
|
||
lock (_lockObject)
|
||
{
|
||
pathCount = _pathVisualizations.Count;
|
||
railBaselineCount = _railBaselineVisualizations.Count;
|
||
hasPreviewPoint = _previewMarker != null;
|
||
}
|
||
|
||
if (pathCount == 0 && railBaselineCount == 0 && !hasPreviewPoint)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 使用BeginModelContext确保正确的渲染上下文
|
||
graphics.BeginModelContext();
|
||
|
||
lock (_lockObject)
|
||
{
|
||
// 渲染所有正式路径
|
||
foreach (var visualization in _pathVisualizations.Values)
|
||
{
|
||
// 渲染控制点可视化(用户意图)
|
||
if (visualization.ShowControlVisualization)
|
||
{
|
||
// 渲染所有点标记
|
||
foreach (var pointMarker in visualization.PointMarkers)
|
||
{
|
||
graphics.Color(pointMarker.Color, pointMarker.Alpha);
|
||
RenderPointMarker(graphics, pointMarker);
|
||
}
|
||
|
||
foreach (var planeMarker in visualization.PlaneMarkers)
|
||
{
|
||
RenderPlaneMarker(graphics, planeMarker);
|
||
}
|
||
|
||
// 渲染控制点连线(半透明)
|
||
foreach (var controlLineMarker in visualization.ControlLineMarkers)
|
||
{
|
||
var style = GetRenderStyle(RenderStyleName.PreviewLine);
|
||
graphics.Color(style.Color, style.Alpha); // 完全使用PreviewLine的颜色和透明度
|
||
graphics.Cylinder(controlLineMarker.StartPoint, controlLineMarker.EndPoint, controlLineMarker.Radius);
|
||
}
|
||
}
|
||
|
||
// 渲染真实路径可视化(使用独立开关,可同时显示多种)
|
||
// 先渲染路径线(地面连线/带状线)
|
||
if (_showPathLines)
|
||
{
|
||
// 先渲染切点(在底层)
|
||
foreach (var tangentMarker in visualization.TangentMarkers)
|
||
{
|
||
RenderSquareMarker(graphics, tangentMarker);
|
||
}
|
||
|
||
// 再渲染路径连线
|
||
foreach (var pathLineMarker in visualization.PathLineMarkers)
|
||
{
|
||
RenderLineMarker(graphics, pathLineMarker);
|
||
}
|
||
}
|
||
|
||
// 再渲染通行空间(可以在路径线之上)
|
||
if (_showObjectSpace)
|
||
{
|
||
// ObjectSpace 模式:使用物体通行空间渲染(高高度长方体)
|
||
foreach (var pathLineMarker in visualization.PathLineMarkers)
|
||
{
|
||
RenderRibbonLineMarker(graphics, pathLineMarker);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 渲染预览点(灰色)
|
||
if (_previewMarker != null && _previewMarker.IsVisible)
|
||
{
|
||
graphics.Color(_previewMarker.Color, 0.7); // 使用半透明效果
|
||
RenderPointMarker(graphics, _previewMarker);
|
||
}
|
||
|
||
// 渲染预览连线(灰色)
|
||
if (_previewLines.Count > 0)
|
||
{
|
||
foreach (var previewLine in _previewLines)
|
||
{
|
||
graphics.Color(previewLine.Color, 0.7); // 使用半透明效果
|
||
graphics.Cylinder(previewLine.StartPoint, previewLine.EndPoint, previewLine.Radius);
|
||
}
|
||
}
|
||
|
||
// 渲染空轨基准路径(浅红色)
|
||
if (_railBaselineVisualizations.Count > 0)
|
||
{
|
||
foreach (var visualization in _railBaselineVisualizations.Values)
|
||
{
|
||
foreach (var pathLineMarker in visualization.PathLineMarkers)
|
||
{
|
||
graphics.Color(pathLineMarker.Color, 0.7); // 使用70%透明度
|
||
graphics.Cylinder(pathLineMarker.StartPoint, pathLineMarker.EndPoint, pathLineMarker.Radius);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
graphics.EndModelContext();
|
||
}
|
||
catch (InvalidOperationException ex) when (ex.Message.Contains("[渲染错误]"))
|
||
{
|
||
// 🔥 参数错误不应该静默处理,需要暴露出来让开发者修复
|
||
LogManager.Error($"[渲染错误] {ex.Message}");
|
||
throw; // 重新抛出,让上层捕获并显示给用户
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 其他渲染异常静默处理,避免影响Navisworks主程序
|
||
LogManager.Error($"[渲染异常] {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
#region 新的统一路径可视化接口
|
||
|
||
/// <summary>
|
||
/// 渲染完整路径
|
||
/// </summary>
|
||
/// <param name="pathRoute">路径数据</param>
|
||
public void RenderPath(PathRoute pathRoute)
|
||
{
|
||
if (pathRoute == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 清除所有其他路径的可视化,只保留当前路径
|
||
ClearPathsExcept(pathRoute.Id);
|
||
|
||
var visualization = new PathVisualization
|
||
{
|
||
PathId = pathRoute.Id,
|
||
PathRoute = pathRoute
|
||
};
|
||
|
||
BuildVisualization(visualization);
|
||
|
||
lock (_lockObject)
|
||
{
|
||
_pathVisualizations[pathRoute.Id] = visualization;
|
||
}
|
||
|
||
RequestViewRefresh();
|
||
}
|
||
catch (InvalidOperationException ex) when (ex.Message.Contains("[渲染错误]"))
|
||
{
|
||
// 🔥 参数错误不应该静默处理,需要暴露出来让开发者修复
|
||
LogManager.Error($"[渲染错误] {ex.Message}");
|
||
throw; // 重新抛出,让上层捕获
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[路径渲染] 渲染路径失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 只渲染点标记,不绘制连线(用于自动路径规划的起终点显示)
|
||
/// </summary>
|
||
public void RenderPointOnly(PathRoute pathRoute)
|
||
{
|
||
if (pathRoute == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 检查是否是网格可视化路径
|
||
bool isGridVisualization = pathRoute.Id == "grid_visualization_all" ||
|
||
pathRoute.Id == "grid_visualization_channel" ||
|
||
pathRoute.Id == "grid_visualization_door" ||
|
||
pathRoute.Id == "grid_visualization_unknown" ||
|
||
pathRoute.Id == "grid_visualization_obstacle";
|
||
|
||
var visualization = new PathVisualization
|
||
{
|
||
PathId = pathRoute.Id,
|
||
PathRoute = pathRoute
|
||
};
|
||
|
||
// 只构建点标记,不构建连线
|
||
BuildPointMarkersOnly(visualization);
|
||
|
||
lock (_lockObject)
|
||
{
|
||
_pathVisualizations[pathRoute.Id] = visualization;
|
||
}
|
||
|
||
RequestViewRefresh();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[点标记渲染] 渲染点标记失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
public void RenderRectanglePlane(
|
||
string pathId,
|
||
Point3D origin,
|
||
Vector3D xVector,
|
||
Vector3D yVector,
|
||
RenderStyleName renderStyleName,
|
||
bool filled = true)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(pathId))
|
||
{
|
||
throw new ArgumentException("pathId 不能为空。", nameof(pathId));
|
||
}
|
||
|
||
try
|
||
{
|
||
var visualization = new PathVisualization
|
||
{
|
||
PathId = pathId,
|
||
PathRoute = new PathRoute(pathId)
|
||
{
|
||
Id = pathId,
|
||
Description = $"矩形平面可视化:{pathId}"
|
||
}
|
||
};
|
||
|
||
var renderStyle = GetRenderStyle(renderStyleName);
|
||
visualization.PlaneMarkers.Add(new PlaneMarker
|
||
{
|
||
Origin = origin,
|
||
XVector = xVector,
|
||
YVector = yVector,
|
||
Color = renderStyle.Color,
|
||
Alpha = renderStyle.Alpha,
|
||
Filled = filled
|
||
});
|
||
|
||
lock (_lockObject)
|
||
{
|
||
_pathVisualizations[pathId] = visualization;
|
||
}
|
||
|
||
RequestViewRefresh();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[平面渲染] 渲染矩形平面失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除网格可视化
|
||
/// </summary>
|
||
public void ClearGridVisualization()
|
||
{
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
// 清除所有网格可视化
|
||
RemovePath("grid_visualization_walkable");
|
||
RemovePath("grid_visualization_obstacle");
|
||
RemovePath("grid_visualization_unknown");
|
||
RemovePath("grid_visualization_door");
|
||
// 清除旧的网格可视化 ID(向后兼容)
|
||
RemovePath("grid_visualization_all");
|
||
RemovePath("grid_visualization_channel");
|
||
}
|
||
|
||
RequestViewRefresh();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[网格可视化] 清除网格可视化失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染网格可视化(使用专门的 GridMarker 类型)
|
||
/// </summary>
|
||
/// <param name="gridVis">网格可视化对象</param>
|
||
public void RenderGridVisualization(GridVisualization gridVis)
|
||
{
|
||
if (gridVis == null)
|
||
{
|
||
LogManager.Warning("[网格可视化] GridVisualization 对象为 null,无法渲染");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 清除之前的网格可视化
|
||
ClearGridVisualization();
|
||
|
||
// 渲染可通行的网格点(绿色)
|
||
if (gridVis.Walkable.Count > 0)
|
||
{
|
||
RenderGridMarkers("grid_visualization_walkable", gridVis.Walkable, GridVisualizationType.Walkable);
|
||
}
|
||
|
||
// 渲染障碍物网格点(灰色)
|
||
if (gridVis.Obstacle.Count > 0)
|
||
{
|
||
RenderGridMarkers("grid_visualization_obstacle", gridVis.Obstacle, GridVisualizationType.Obstacle);
|
||
}
|
||
|
||
// 渲染未知区域网格点(红色)
|
||
if (gridVis.Unknown.Count > 0)
|
||
{
|
||
RenderGridMarkers("grid_visualization_unknown", gridVis.Unknown, GridVisualizationType.Unknown);
|
||
}
|
||
|
||
// 渲染门网格点(半透明绿色)
|
||
if (gridVis.Door.Count > 0)
|
||
{
|
||
RenderGridMarkers("grid_visualization_door", gridVis.Door, GridVisualizationType.Door);
|
||
}
|
||
|
||
RequestViewRefresh();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[网格可视化] 渲染失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染一组网格标记
|
||
/// </summary>
|
||
/// <param name="pathId">路径 ID</param>
|
||
/// <param name="markers">网格标记列表</param>
|
||
/// <param name="cellType">网格可视化类型</param>
|
||
private void RenderGridMarkers(string pathId, List<GridMarker> markers, GridVisualizationType cellType)
|
||
{
|
||
if (markers == null || markers.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var visualization = new PathVisualization
|
||
{
|
||
PathId = pathId,
|
||
PathRoute = null // 网格可视化不使用 PathRoute
|
||
};
|
||
|
||
// 创建点标记
|
||
foreach (var marker in markers)
|
||
{
|
||
var pointMarker = CreateGridMarker(marker);
|
||
visualization.PointMarkers.Add(pointMarker);
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
_pathVisualizations[pathId] = visualization;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建网格标记(类型安全,无需字符串匹配)
|
||
/// </summary>
|
||
/// <param name="marker">网格标记</param>
|
||
/// <returns>圆点标记对象</returns>
|
||
private CircleMarker CreateGridMarker(GridMarker marker)
|
||
{
|
||
// 根据网格可视化类型确定颜色和透明度
|
||
RenderStyle style = new RenderStyle(Color.White, 1.0);
|
||
switch (marker.CellType)
|
||
{
|
||
case GridVisualizationType.Walkable:
|
||
style = GetRenderStyle(RenderStyleName.GridChannel);
|
||
break;
|
||
case GridVisualizationType.Obstacle:
|
||
style = GetRenderStyle(RenderStyleName.GridObstacle);
|
||
break;
|
||
case GridVisualizationType.Unknown:
|
||
style = GetRenderStyle(RenderStyleName.GridUnknown);
|
||
break;
|
||
case GridVisualizationType.Door:
|
||
// 门使用50%透明度
|
||
style = new RenderStyle(GetRenderStyle(RenderStyleName.GridChannel).Color, 0.5);
|
||
break;
|
||
}
|
||
|
||
return new CircleMarker
|
||
{
|
||
Center = marker.Position,
|
||
Normal = GetHostUpVector(),
|
||
Radius = GetRadiusForGridVisualization(),
|
||
Color = style.Color,
|
||
Alpha = style.Alpha,
|
||
Filled = true,
|
||
PointType = PathPointType.WayPoint,
|
||
GridPointType = _gridPointType,
|
||
SequenceNumber = 0,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 只构建点标记,不构建连线
|
||
/// </summary>
|
||
private void BuildPointMarkersOnly(PathVisualization visualization)
|
||
{
|
||
// 清空现有标记
|
||
visualization.PointMarkers.Clear();
|
||
visualization.ControlLineMarkers.Clear(); // 确保没有控制点连线
|
||
visualization.PathLineMarkers.Clear(); // 确保没有路径连线
|
||
visualization.PlaneMarkers.Clear();
|
||
|
||
var points = visualization.PathRoute.Points;
|
||
if (points.Count == 0) return;
|
||
|
||
// 检查是否是网格可视化路径
|
||
bool isGridVisualization = visualization.PathId == "grid_visualization_all" ||
|
||
visualization.PathId == "grid_visualization_channel" ||
|
||
visualization.PathId == "grid_visualization_door" ||
|
||
visualization.PathId == "grid_visualization_unknown" ||
|
||
visualization.PathId == "grid_visualization_obstacle";
|
||
|
||
// 按索引排序点
|
||
var sortedPoints = points.OrderBy(p => p.Index).ToList();
|
||
|
||
// 只构建点标记,不构建连线
|
||
foreach (var point in sortedPoints)
|
||
{
|
||
var pointMarker = CreatePointMarker(point);
|
||
visualization.PointMarkers.Add(pointMarker);
|
||
}
|
||
|
||
visualization.LastUpdated = DateTime.Now;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新路径可视化
|
||
/// </summary>
|
||
/// <param name="pathId">路径ID</param>
|
||
public void RefreshPath(string pathId)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
|
||
{
|
||
BuildVisualization(visualization);
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置控制点可视化的显示状态
|
||
/// </summary>
|
||
/// <param name="pathId">路径ID</param>
|
||
/// <param name="show">是否显示</param>
|
||
public void SetControlVisualizationVisibility(string pathId, bool show)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
|
||
{
|
||
if (visualization.ShowControlVisualization != show)
|
||
{
|
||
visualization.ShowControlVisualization = show;
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换控制点可视化的显示状态
|
||
/// </summary>
|
||
/// <param name="pathId">路径ID</param>
|
||
public void ToggleControlVisualization(string pathId)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
|
||
{
|
||
visualization.ShowControlVisualization = !visualization.ShowControlVisualization;
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有路径可视化对象
|
||
/// </summary>
|
||
/// <returns>所有路径可视化对象的列表</returns>
|
||
public List<PathVisualization> GetAllPathVisualizations()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
return _pathVisualizations.Values.ToList();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断是否为网格可视化路径
|
||
/// </summary>
|
||
private bool IsGridVisualizationPath(string pathId)
|
||
{
|
||
return pathId != null && (
|
||
pathId.StartsWith("grid_visualization_") ||
|
||
pathId == "grid_visualization_all" ||
|
||
pathId == "voxel_grid_visualization"); // 体素网格可视化
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新所有普通路径(排除网格可视化)
|
||
/// </summary>
|
||
private void RefreshNormalPaths()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
var normalPathIds = _pathVisualizations.Keys
|
||
.Where(id => !IsGridVisualizationPath(id))
|
||
.ToList();
|
||
|
||
foreach (var pathId in normalPathIds)
|
||
{
|
||
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
|
||
{
|
||
BuildVisualization(visualization);
|
||
}
|
||
}
|
||
|
||
if (normalPathIds.Count > 0)
|
||
{
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新所有网格路径
|
||
/// </summary>
|
||
private void RefreshGridPaths()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
var gridPathIds = _pathVisualizations.Keys
|
||
.Where(id => IsGridVisualizationPath(id))
|
||
.ToList();
|
||
|
||
foreach (var pathId in gridPathIds)
|
||
{
|
||
if (_pathVisualizations.TryGetValue(pathId, out var visualization))
|
||
{
|
||
BuildPointMarkersOnly(visualization);
|
||
}
|
||
}
|
||
|
||
if (gridPathIds.Count > 0)
|
||
{
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除路径
|
||
/// </summary>
|
||
/// <param name="pathId">路径ID</param>
|
||
public void RemovePath(string pathId)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (_pathVisualizations.Remove(pathId))
|
||
{
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染空轨基准路径
|
||
/// </summary>
|
||
/// <param name="railModelId">空轨模型ID</param>
|
||
/// <param name="baselinePoints">基准路径点列表</param>
|
||
public void RenderRailBaseline(string railModelId, List<Point3D> baselinePoints)
|
||
{
|
||
RenderRailBaseline(railModelId, baselinePoints, RenderStyleName.RailBaseline);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按指定样式渲染基准路径。
|
||
/// </summary>
|
||
public void RenderRailBaseline(string railModelId, List<Point3D> baselinePoints, RenderStyleName renderStyleName)
|
||
{
|
||
if (string.IsNullOrEmpty(railModelId) || baselinePoints == null || baselinePoints.Count < 2)
|
||
{
|
||
LogManager.Warning($"[路径渲染] RenderRailBaseline参数无效: railModelId={railModelId}, points={(baselinePoints?.Count ?? 0)}");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 先清除该模型的旧基准路径可视化
|
||
ClearRailBaseline(railModelId);
|
||
|
||
// 创建PathVisualization对象
|
||
var visualization = new PathVisualization
|
||
{
|
||
PathId = railModelId,
|
||
ShowControlVisualization = false, // 不显示控制点
|
||
LastUpdated = DateTime.Now
|
||
};
|
||
|
||
var renderStyle = GetRenderStyle(renderStyleName);
|
||
|
||
// 创建连线标记
|
||
for (int i = 0; i < baselinePoints.Count - 1; i++)
|
||
{
|
||
var start = baselinePoints[i];
|
||
var end = baselinePoints[i + 1];
|
||
|
||
var lineMarker = new LineMarker
|
||
{
|
||
StartPoint = start,
|
||
EndPoint = end,
|
||
Color = renderStyle.Color,
|
||
Radius = GetLineRadius() * 0.2,
|
||
SegmentType = PathSegmentType.Straight,
|
||
FromIndex = i,
|
||
ToIndex = i + 1
|
||
};
|
||
|
||
visualization.PathLineMarkers.Add(lineMarker);
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
_railBaselineVisualizations[railModelId] = visualization;
|
||
}
|
||
|
||
RequestViewRefresh();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[路径渲染] 渲染空轨基准路径失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除指定空轨的基准路径可视化
|
||
/// </summary>
|
||
/// <param name="railModelId">空轨模型ID</param>
|
||
public void ClearRailBaseline(string railModelId)
|
||
{
|
||
if (string.IsNullOrEmpty(railModelId))
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
if (_railBaselineVisualizations.Remove(railModelId))
|
||
{
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有空轨基准路径
|
||
/// </summary>
|
||
/// <returns>所有空轨基准路径的字典</returns>
|
||
public Dictionary<string, List<Point3D>> GetAllRailBaselinePaths()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
var result = new Dictionary<string, List<Point3D>>();
|
||
foreach (var kvp in _railBaselineVisualizations)
|
||
{
|
||
var pathPoints = kvp.Value.PathLineMarkers.Select(m => m.StartPoint).ToList();
|
||
var lastMarker = kvp.Value.PathLineMarkers.LastOrDefault();
|
||
if (lastMarker != null)
|
||
{
|
||
pathPoints.Add(lastMarker.EndPoint);
|
||
}
|
||
|
||
result[kvp.Key] = pathPoints;
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除所有空轨基准路径可视化
|
||
/// </summary>
|
||
public void ClearAllRailBaselines()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
int count = _railBaselineVisualizations.Count;
|
||
_railBaselineVisualizations.Clear();
|
||
if (count > 0)
|
||
{
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空所有路径
|
||
/// </summary>
|
||
public void ClearAllPaths()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
var count = _pathVisualizations.Count;
|
||
_pathVisualizations.Clear();
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空路径,但保留指定的路径
|
||
/// </summary>
|
||
/// <param name="excludedPathIds">要保留的路径ID列表</param>
|
||
public void ClearPathsExcept(params string[] excludedPathIds)
|
||
{
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
// 防御性编程:处理空参数
|
||
if (excludedPathIds == null)
|
||
{
|
||
excludedPathIds = new string[0];
|
||
}
|
||
|
||
var excludedSet = new HashSet<string>(excludedPathIds.Where(id => !string.IsNullOrEmpty(id)));
|
||
var toRemove = _pathVisualizations.Keys.Where(id => !excludedSet.Contains(id)).ToList();
|
||
|
||
// 检查排除的路径是否实际存在
|
||
var existingExcluded = excludedSet.Where(id => _pathVisualizations.ContainsKey(id)).ToList();
|
||
var nonExistingExcluded = excludedSet.Where(id => !_pathVisualizations.ContainsKey(id)).ToList();
|
||
|
||
int removedCount = 0;
|
||
foreach (var pathId in toRemove)
|
||
{
|
||
if (_pathVisualizations.Remove(pathId))
|
||
{
|
||
removedCount++;
|
||
}
|
||
}
|
||
|
||
if (removedCount > 0)
|
||
{
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[选择性清空] 清理路径时发生异常: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建路径可视化
|
||
/// </summary>
|
||
/// <param name="visualization">路径可视化对象</param>
|
||
private void BuildVisualization(PathVisualization visualization)
|
||
{
|
||
// 清空现有标记
|
||
visualization.PointMarkers.Clear();
|
||
visualization.ControlLineMarkers.Clear();
|
||
visualization.PathLineMarkers.Clear();
|
||
visualization.TangentMarkers.Clear();
|
||
visualization.ObjectSpaceMarkers.Clear();
|
||
|
||
var points = visualization.PathRoute.Points;
|
||
if (points.Count == 0) return;
|
||
|
||
// 按索引排序点
|
||
var sortedPoints = points.OrderBy(p => p.Index).ToList();
|
||
|
||
// 构建点标记(所有控制点)
|
||
foreach (var point in sortedPoints)
|
||
{
|
||
var pointMarker = CreatePointMarker(point);
|
||
visualization.PointMarkers.Add(pointMarker);
|
||
}
|
||
|
||
// 构建控制点连线(用户意图,半透明)
|
||
BuildControlLines(visualization, sortedPoints);
|
||
|
||
// 地面路径使用曲线化后的路径(Edges)
|
||
// 空中路径只显示控制点连线,不显示路径连线(除非启用通行空间可视化)
|
||
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail ||
|
||
visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||
{
|
||
// 空中路径:默认只显示控制点连线
|
||
// 但如果启用了通行空间可视化,则构建路径连线以显示通行空间
|
||
if (_showObjectSpace)
|
||
{
|
||
string aerialSubTypeName = visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail ? "空轨" : "吊装";
|
||
BuildPathLines(visualization, sortedPoints);
|
||
LogManager.Debug($"[BuildVisualization] {aerialSubTypeName}路径已启用通行空间,构建路径连线");
|
||
}
|
||
else
|
||
{
|
||
string aerialSubTypeName = visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail ? "空轨" : "吊装";
|
||
LogManager.Debug($"[BuildVisualization] {aerialSubTypeName}路径未启用通行空间,仅显示控制点连线");
|
||
}
|
||
}
|
||
else if (visualization.PathRoute.Edges != null && visualization.PathRoute.Edges.Count > 0)
|
||
{
|
||
// 地面路径:使用曲线化后的路径
|
||
BuildPathLines(visualization, sortedPoints);
|
||
}
|
||
|
||
// 如果路径未完成,添加灰色的原始终点标记
|
||
if (!visualization.PathRoute.IsComplete && visualization.PathRoute.OriginalEndPoint != null)
|
||
{
|
||
var originalEndPoint = visualization.PathRoute.OriginalEndPoint;
|
||
var unreachedStyle = GetRenderStyle(RenderStyleName.UnreachedEndPoint);
|
||
var grayEndMarker = new CircleMarker
|
||
{
|
||
Center = originalEndPoint,
|
||
Normal = GetHostUpVector(),
|
||
Radius = GetRadiusForPointType(PathPointType.EndPoint),
|
||
Color = unreachedStyle.Color, // 未到达终点颜色
|
||
Alpha = unreachedStyle.Alpha, // 透明度,统一管理
|
||
Filled = true,
|
||
PointType = PathPointType.EndPoint,
|
||
SequenceNumber = -1, // 特殊序号表示这是未到达的终点
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
visualization.PointMarkers.Add(grayEndMarker);
|
||
}
|
||
|
||
visualization.LastUpdated = DateTime.Now;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建控制点连线(用户意图,半透明)
|
||
/// </summary>
|
||
/// <param name="visualization">路径可视化对象</param>
|
||
/// <param name="sortedPoints">排序后的路径点列表</param>
|
||
private void BuildControlLines(PathVisualization visualization, List<PathPoint> sortedPoints)
|
||
{
|
||
// 根据路径类型选择连线样式
|
||
RenderStyleName lineStyleName = RenderStyleName.PreviewLine;
|
||
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail ||
|
||
visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||
{
|
||
lineStyleName = RenderStyleName.HoistingLine;
|
||
}
|
||
|
||
// 构建控制点之间的连线(所有相邻控制点)
|
||
for (int i = 0; i < sortedPoints.Count - 1; i++)
|
||
{
|
||
var currentPoint = sortedPoints[i];
|
||
var nextPoint = sortedPoints[i + 1];
|
||
|
||
var controlLineMarker = new LineMarker
|
||
{
|
||
StartPoint = currentPoint.Position,
|
||
EndPoint = nextPoint.Position,
|
||
Color = GetRenderStyle(lineStyleName).Color,
|
||
Radius = GetLineRadius() * 0.5, // 比实际路径细
|
||
SegmentType = PathSegmentType.Straight,
|
||
FromIndex = currentPoint.Index,
|
||
ToIndex = nextPoint.Index
|
||
};
|
||
visualization.ControlLineMarkers.Add(controlLineMarker);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算通行空间参数(高度、宽度、颜色、透明度、垂直偏移)
|
||
/// </summary>
|
||
/// <param name="visualization">路径可视化对象</param>
|
||
/// <param name="isVerticalSegment">是否是垂直段(仅用于吊装路径)</param>
|
||
/// <param name="isTurn90Horizontal">是否是与初始方向垂直的水平段(吊装路径转折90度)</param>
|
||
/// <param name="isFirstOrLastSegment">是否是第一个或最后一个线段(起吊/下降段)</param>
|
||
/// <returns>通行空间参数元组</returns>
|
||
private (double height, double width, Color lineColor, double lineOpacity, double verticalOffset)
|
||
CalculatePassageSpaceParameters(PathVisualization visualization, bool isVerticalSegment = false, bool isTurn90Horizontal = false, bool isFirstOrLastSegment = false)
|
||
{
|
||
// 🔥 验证通行空间参数是否已正确设置
|
||
if (_passageAcrossPath <= 0 || _passageNormalToPath <= 0 || _passageAlongPath <= 0)
|
||
{
|
||
throw new InvalidOperationException(
|
||
$"[渲染错误] 通行空间参数未正确设置: " +
|
||
$"_passageAcrossPath={_passageAcrossPath:F4}, " +
|
||
$"_passageNormalToPath={_passageNormalToPath:F4}, " +
|
||
$"_passageAlongPath={_passageAlongPath:F4}. " +
|
||
$"请确保在渲染前调用 SetPassageSpaceParameters()");
|
||
}
|
||
|
||
double height;
|
||
double width;
|
||
Color lineColor;
|
||
double lineOpacity;
|
||
|
||
if (_showObjectSpace)
|
||
{
|
||
// 通行空间模式:根据路径类型和段类型计算通行空间尺寸
|
||
// _passage* 和 _object* 字段已经是模型单位(由 SetPassageSpaceParameters 转换)
|
||
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||
{
|
||
// 吊装路径:根据段类型和方向设置尺寸
|
||
// 默认:width=物体宽度(垂直于路径),height=物体高度/长度
|
||
width = _passageAcrossPath;
|
||
height = isVerticalSegment ? _objectLength : _objectHeight;
|
||
|
||
// 🔥 水平段转折90度特殊处理:width改用物体长度
|
||
if (!isVerticalSegment && isTurn90Horizontal)
|
||
{
|
||
width = _objectLength;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 其他路径类型
|
||
width = _passageAcrossPath;
|
||
height = _passageNormalToPath;
|
||
}
|
||
|
||
var renderStyle = GetRenderStyle(RenderStyleName.ObjectSpace);
|
||
lineColor = renderStyle.Color;
|
||
lineOpacity = renderStyle.Alpha;
|
||
}
|
||
else if (_showPathLines)
|
||
{
|
||
// RibbonLine 模式:使用较低的带状高度
|
||
// GetLineRadius() 返回模型单位,直接乘以系数
|
||
height = GetLineRadius() * 0.1;
|
||
width = _passageAcrossPath; // 已经是模型单位
|
||
LogManager.Debug($"[CalculatePassageSpaceParameters] RibbonLine模式: _passageAcrossPath={_passageAcrossPath:F4}, width={width:F4}, height={height:F4}");
|
||
var renderStyle = GetRenderStyle(RenderStyleName.Line);
|
||
lineColor = renderStyle.Color;
|
||
lineOpacity = renderStyle.Alpha;
|
||
}
|
||
else
|
||
{
|
||
// StandardLine 模式:不需要高度和宽度(圆柱体)
|
||
height = 0;
|
||
width = 0;
|
||
var renderStyle = GetRenderStyle(RenderStyleName.Line);
|
||
lineColor = renderStyle.Color;
|
||
lineOpacity = renderStyle.Alpha;
|
||
}
|
||
|
||
// 计算通行空间的垂直偏移(根据路径类型)
|
||
double verticalOffset = 0;
|
||
if (_showObjectSpace)
|
||
{
|
||
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Ground)
|
||
{
|
||
// 地面路径:路径点是地面位置,通行空间底面在地面,中心需要向上偏移半个高度
|
||
verticalOffset = ResolveGroundPathObjectSpaceVerticalOffset(height);
|
||
}
|
||
else if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail)
|
||
{
|
||
verticalOffset = RailPathPoseHelper.GetObjectSpaceCenterZOffset(
|
||
visualization.PathRoute,
|
||
height);
|
||
}
|
||
else if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||
{
|
||
// 吊装路径:根据段类型设置偏移
|
||
if (isVerticalSegment)
|
||
{
|
||
// 所有垂直段(起吊、下降、层间过渡):顶面在高点,不需要偏移
|
||
// 起吊/下降段:底面在地面(起点/终点)
|
||
// 层间过渡:底面在低点以下物体长度(通过延伸实现)
|
||
verticalOffset = 0;
|
||
}
|
||
else
|
||
{
|
||
// 水平段:顶面中心对齐路径点,向下偏移半个高度
|
||
verticalOffset = -height / 2.0;
|
||
}
|
||
}
|
||
}
|
||
|
||
return (height, width, lineColor, lineOpacity, verticalOffset);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建路径连线(使用Edges,包含直线段和圆弧段)
|
||
/// </summary>
|
||
/// <param name="visualization">路径可视化对象</param>
|
||
/// <param name="sortedPoints">排序后的路径点列表</param>
|
||
private void BuildPathLines(PathVisualization visualization, List<PathPoint> sortedPoints)
|
||
{
|
||
var edges = visualization.PathRoute.Edges;
|
||
|
||
// 处理 Edges 为空的情况(如吊装路径未经过曲线化)
|
||
if (edges == null || edges.Count == 0)
|
||
{
|
||
BuildDirectPathLines(visualization, sortedPoints);
|
||
return;
|
||
}
|
||
|
||
// 1. 收集在路径上的控制点ID
|
||
var onPathPointIds = new HashSet<string>();
|
||
foreach (var edge in edges)
|
||
{
|
||
if (!string.IsNullOrEmpty(edge.StartPointId))
|
||
onPathPointIds.Add(edge.StartPointId);
|
||
if (!string.IsNullOrEmpty(edge.EndPointId))
|
||
onPathPointIds.Add(edge.EndPointId);
|
||
}
|
||
|
||
// 2. 更新点标记的透明度
|
||
foreach (var pointMarker in visualization.PointMarkers)
|
||
{
|
||
var point = sortedPoints.FirstOrDefault(p => p.Id == pointMarker.PathPoint?.Id);
|
||
if (point != null)
|
||
{
|
||
bool isOnPath = onPathPointIds.Contains(point.Id);
|
||
pointMarker.IsOffPath = !isOnPath;
|
||
}
|
||
}
|
||
|
||
// 获取单位转换系数
|
||
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||
|
||
// 计算通行空间参数
|
||
var (height, width, lineColor, lineOpacity, verticalOffset) =
|
||
CalculatePassageSpaceParameters(visualization);
|
||
|
||
// 3. 构建路径连线(Edges包含直线段和圆弧段)
|
||
foreach (var edge in edges)
|
||
{
|
||
if (edge.SegmentType == PathSegmentType.Straight)
|
||
{
|
||
// 直线段:检查SampledPoints是否有效
|
||
if (edge.SampledPoints != null && edge.SampledPoints.Count >= 2)
|
||
{
|
||
var startPoint = edge.SampledPoints.First();
|
||
var endPoint = edge.SampledPoints.Last();
|
||
|
||
// 计算长方体的轴向量(right和up向量)
|
||
var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute);
|
||
var (right, up) = CalculateCuboidAxes(startPoint, endPoint, preferredUp);
|
||
|
||
// 根据路径类型调整起点和终点位置
|
||
// 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移
|
||
Point3D adjustedStartPoint;
|
||
Point3D adjustedEndPoint;
|
||
|
||
if (_showObjectSpace && visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail)
|
||
{
|
||
adjustedStartPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
|
||
visualization.PathRoute,
|
||
startPoint,
|
||
startPoint,
|
||
endPoint,
|
||
height);
|
||
adjustedEndPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
|
||
visualization.PathRoute,
|
||
endPoint,
|
||
startPoint,
|
||
endPoint,
|
||
height);
|
||
}
|
||
else if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001)
|
||
{
|
||
// 非 Rail 通行空间必须沿宿主 up 偏移,不能写死世界 Z。
|
||
var hostUp = GetHostUpVector();
|
||
adjustedStartPoint = ApplyVerticalOffset(startPoint, hostUp, verticalOffset);
|
||
adjustedEndPoint = ApplyVerticalOffset(endPoint, hostUp, verticalOffset);
|
||
}
|
||
else
|
||
{
|
||
// 其他情况,不调整起点和终点位置
|
||
adjustedStartPoint = startPoint;
|
||
adjustedEndPoint = endPoint;
|
||
}
|
||
|
||
// 🔥 地面/空轨路径水平段:起点和终点向路径方向延伸半个物体尺寸(仅通行空间)
|
||
if (_showObjectSpace)
|
||
{
|
||
ExtendLineSegmentForObjectSpace(ref adjustedStartPoint, ref adjustedEndPoint, _passageAlongPath);
|
||
}
|
||
|
||
var lineMarker = new LineMarker
|
||
{
|
||
StartPoint = adjustedStartPoint,
|
||
EndPoint = adjustedEndPoint,
|
||
Color = lineColor,
|
||
Radius = GetLineRadius(),
|
||
SegmentType = PathSegmentType.Straight,
|
||
SampledPoints = edge.SampledPoints,
|
||
Height = height, // 根据模式设置高度
|
||
Width = width, // 根据模式设置宽度
|
||
Opacity = lineOpacity, // 根据模式设置透明度
|
||
UpDirection = preferredUp
|
||
};
|
||
visualization.PathLineMarkers.Add(lineMarker);
|
||
}
|
||
}
|
||
else if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null)
|
||
{
|
||
// 圆弧段:检查Trajectory是否有效
|
||
|
||
// 检查是否发生安全截断
|
||
bool hasSafetyTruncation = edge.Trajectory.ActualRadius < edge.Trajectory.RequestedRadius;
|
||
|
||
// 如果发生安全截断,使用安全警告样式
|
||
Color arcLineColor = lineColor;
|
||
double arcLineOpacity = lineOpacity;
|
||
if (hasSafetyTruncation)
|
||
{
|
||
var warningStyle = GetRenderStyle(RenderStyleName.SafetyWarning);
|
||
arcLineColor = warningStyle.Color;
|
||
arcLineOpacity = warningStyle.Alpha;
|
||
}
|
||
|
||
var startPoint = edge.Trajectory.Ts;
|
||
var endPoint = edge.Trajectory.Te;
|
||
|
||
// 计算长方体的轴向量(right和up向量)
|
||
var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute);
|
||
var (right, up) = CalculateCuboidAxes(startPoint, endPoint, preferredUp);
|
||
|
||
// 根据路径类型调整起点和终点位置
|
||
// 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移
|
||
Point3D adjustedStartPoint;
|
||
Point3D adjustedEndPoint;
|
||
List<Point3D> adjustedSampledPoints;
|
||
|
||
if (_showObjectSpace && visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail)
|
||
{
|
||
adjustedStartPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
|
||
visualization.PathRoute,
|
||
startPoint,
|
||
startPoint,
|
||
endPoint,
|
||
height);
|
||
adjustedEndPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
|
||
visualization.PathRoute,
|
||
endPoint,
|
||
startPoint,
|
||
endPoint,
|
||
height);
|
||
|
||
adjustedSampledPoints = new List<Point3D>();
|
||
if (edge.SampledPoints != null)
|
||
{
|
||
for (int sampleIndex = 0; sampleIndex < edge.SampledPoints.Count; sampleIndex++)
|
||
{
|
||
var currentPoint = edge.SampledPoints[sampleIndex];
|
||
var previousPoint = sampleIndex > 0 ? edge.SampledPoints[sampleIndex - 1] : currentPoint;
|
||
var nextPoint = sampleIndex < edge.SampledPoints.Count - 1 ? edge.SampledPoints[sampleIndex + 1] : currentPoint;
|
||
adjustedSampledPoints.Add(RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
|
||
visualization.PathRoute,
|
||
currentPoint,
|
||
previousPoint,
|
||
nextPoint,
|
||
height));
|
||
}
|
||
}
|
||
}
|
||
else if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001)
|
||
{
|
||
// 非 Rail 通行空间必须沿宿主 up 偏移,不能写死世界 Z。
|
||
var hostUp = GetHostUpVector();
|
||
adjustedStartPoint = ApplyVerticalOffset(startPoint, hostUp, verticalOffset);
|
||
adjustedEndPoint = ApplyVerticalOffset(endPoint, hostUp, verticalOffset);
|
||
|
||
// 对 SampledPoints 应用同一宿主 up 方向偏移。
|
||
adjustedSampledPoints = new List<Point3D>();
|
||
if (edge.SampledPoints != null)
|
||
{
|
||
foreach (var point in edge.SampledPoints)
|
||
{
|
||
adjustedSampledPoints.Add(ApplyVerticalOffset(point, hostUp, verticalOffset));
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 其他情况,不调整起点和终点位置
|
||
adjustedStartPoint = startPoint;
|
||
adjustedEndPoint = endPoint;
|
||
adjustedSampledPoints = edge.SampledPoints;
|
||
}
|
||
|
||
// 🔥 圆弧段:起点和终点向路径方向延伸半个物体尺寸(仅通行空间)
|
||
if (_showObjectSpace)
|
||
{
|
||
ExtendLineSegmentForObjectSpace(ref adjustedStartPoint, ref adjustedEndPoint, _passageAlongPath);
|
||
}
|
||
|
||
var arcMarker = new LineMarker
|
||
{
|
||
StartPoint = adjustedStartPoint,
|
||
EndPoint = adjustedEndPoint,
|
||
Color = arcLineColor,
|
||
Radius = GetLineRadius(),
|
||
SegmentType = PathSegmentType.Arc,
|
||
Trajectory = edge.Trajectory,
|
||
SampledPoints = adjustedSampledPoints,
|
||
Height = height, // 根据模式设置高度
|
||
Width = width, // 根据模式设置宽度
|
||
Opacity = arcLineOpacity, // 根据模式设置透明度
|
||
UpDirection = preferredUp
|
||
};
|
||
visualization.PathLineMarkers.Add(arcMarker);
|
||
|
||
// 添加切点标记(路径线模式才添加)
|
||
if (_showPathLines)
|
||
{
|
||
AddTangentMarkers(visualization, edge);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算长方体的轴向量(right和up向量)
|
||
/// </summary>
|
||
/// <param name="startPoint">路径段起点</param>
|
||
/// <param name="endPoint">路径段终点</param>
|
||
/// <param name="horizontalDirection">水平段方向向量(用于垂直路径渲染时确定通行空间方向)</param>
|
||
/// <returns>元组(right向量,up向量)</returns>
|
||
private (Vector3D right, Vector3D up) CalculateCuboidAxes(Point3D startPoint, Point3D endPoint, Vector3D upReference = null, Vector3D horizontalDirection = null)
|
||
{
|
||
return ObjectSpaceOrientationHelper.CalculateAxes(
|
||
startPoint,
|
||
endPoint,
|
||
upReference ?? GetHostUpVector(),
|
||
horizontalDirection);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 直接使用路径点构建直线连线(用于吊装路径等未曲线化的路径)
|
||
/// </summary>
|
||
/// <param name="visualization">路径可视化对象</param>
|
||
/// <param name="sortedPoints">排序后的路径点列表</param>
|
||
private void BuildDirectPathLines(PathVisualization visualization, List<PathPoint> sortedPoints)
|
||
{
|
||
if (sortedPoints == null || sortedPoints.Count < 2)
|
||
return;
|
||
|
||
// 获取单位转换系数
|
||
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||
|
||
// 对于吊装路径,预先计算水平段的方向向量
|
||
Vector3D horizontalDirection = null;
|
||
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting &&
|
||
sortedPoints.Count >= 3)
|
||
{
|
||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||
horizontalDirection = ToVector3D(HoistingCoordinateHelper.GetHorizontalDirection3(
|
||
ToVector3(sortedPoints[1].Position),
|
||
ToVector3(sortedPoints[2].Position),
|
||
adapter));
|
||
}
|
||
|
||
// 直接使用路径点构建直线连线
|
||
for (int i = 0; i < sortedPoints.Count - 1; i++)
|
||
{
|
||
var startPoint = sortedPoints[i];
|
||
var endPoint = sortedPoints[i + 1];
|
||
double segmentDx = endPoint.Position.X - startPoint.Position.X;
|
||
double segmentDy = endPoint.Position.Y - startPoint.Position.Y;
|
||
double segmentDz = endPoint.Position.Z - startPoint.Position.Z;
|
||
double segmentLength = Math.Sqrt(
|
||
segmentDx * segmentDx +
|
||
segmentDy * segmentDy +
|
||
segmentDz * segmentDz);
|
||
|
||
if (segmentLength < 0.001)
|
||
{
|
||
LogManager.Debug($"[路径渲染] 跳过零长度路径段: 索引={i}, 路径={visualization.PathRoute?.Name}");
|
||
continue;
|
||
}
|
||
|
||
// 对于吊装路径,判断是否是垂直段和起吊/下降段
|
||
bool isVerticalSegment = false;
|
||
bool isFirstOrLastSegment = false;
|
||
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||
{
|
||
// 吊装路径:支持动态数量的路径点
|
||
// 路径点结构:
|
||
// 点0(起吊点):地面位置
|
||
// 点1(提升点):悬挂点
|
||
// 点2...点N-2:中间悬挂点(用户添加的转向点)
|
||
// 点N-1(下降点):悬挂点
|
||
// 点N(落地点):地面位置
|
||
//
|
||
// 线段结构:
|
||
// 线段0:起吊段(垂直)
|
||
// 线段1...线段N-2:平移段(水平)
|
||
// 线段N-1:下降段(垂直)
|
||
|
||
int totalSegments = sortedPoints.Count - 1;
|
||
int firstSegment = 0; // 起吊段
|
||
int lastSegment = totalSegments - 1; // 下降段
|
||
|
||
// 沿宿主 up 判断垂直段,避免把世界 Z 误当成向上轴
|
||
var hostUp = GetHostUpVector();
|
||
double upDelta = segmentDx * hostUp.X + segmentDy * hostUp.Y + segmentDz * hostUp.Z;
|
||
double horizontalDist = Math.Sqrt(Math.Max(0.0, segmentLength * segmentLength - upDelta * upDelta));
|
||
// 如果沿宿主 up 的变化占主导(垂直段),或者是第一个/最后一个线段
|
||
bool isZDominant = Math.Abs(upDelta) > horizontalDist * 2.0;
|
||
|
||
// 第一个线段和最后一个线段是起吊/下降段
|
||
// 中间的垂直段是层间过渡
|
||
isVerticalSegment = (i == firstSegment || i == lastSegment || isZDominant);
|
||
|
||
// 检测是否是第一个或最后一个线段(起吊/下降段)
|
||
isFirstOrLastSegment = (i == firstSegment || i == lastSegment);
|
||
}
|
||
|
||
// 计算通行空间参数(根据段类型)
|
||
// 对于吊装路径水平段,检测是否转折90度
|
||
bool isTurn90Horizontal = false;
|
||
if (!isVerticalSegment && visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting && horizontalDirection != null)
|
||
{
|
||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||
var currentDirection = ToVector3D(HoistingCoordinateHelper.GetHorizontalDirection3(
|
||
ToVector3(startPoint.Position),
|
||
ToVector3(endPoint.Position),
|
||
adapter));
|
||
var currentLength = Math.Sqrt(
|
||
currentDirection.X * currentDirection.X +
|
||
currentDirection.Y * currentDirection.Y +
|
||
currentDirection.Z * currentDirection.Z);
|
||
if (currentLength > 0.001)
|
||
{
|
||
// 计算与初始水平段方向的点积
|
||
double dotProduct =
|
||
currentDirection.X * horizontalDirection.X +
|
||
currentDirection.Y * horizontalDirection.Y +
|
||
currentDirection.Z * horizontalDirection.Z;
|
||
// 点积接近0表示垂直(90度转折)
|
||
isTurn90Horizontal = Math.Abs(dotProduct) < 0.1;
|
||
}
|
||
}
|
||
|
||
var (height, width, lineColor, lineOpacity, verticalOffset) =
|
||
CalculatePassageSpaceParameters(visualization, isVerticalSegment, isTurn90Horizontal, isFirstOrLastSegment);
|
||
|
||
// 计算长方体的轴向量(right和up向量)
|
||
var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute);
|
||
var (right, up) = CalculateCuboidAxes(startPoint.Position, endPoint.Position, preferredUp, horizontalDirection);
|
||
|
||
// 根据路径类型调整起点和终点位置
|
||
// 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移,而不是简单地添加到Z坐标上
|
||
Point3D adjustedStartPoint;
|
||
Point3D adjustedEndPoint;
|
||
|
||
if (_showObjectSpace && visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail)
|
||
{
|
||
adjustedStartPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
|
||
visualization.PathRoute,
|
||
startPoint.Position,
|
||
startPoint.Position,
|
||
endPoint.Position,
|
||
height);
|
||
adjustedEndPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
|
||
visualization.PathRoute,
|
||
endPoint.Position,
|
||
startPoint.Position,
|
||
endPoint.Position,
|
||
height);
|
||
}
|
||
else if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001)
|
||
{
|
||
// 非 Rail 通行空间统一沿宿主 up 方向偏移,不能再默认世界 Z。
|
||
var hostUp = GetHostUpVector();
|
||
adjustedStartPoint = ApplyVerticalOffset(startPoint.Position, hostUp, verticalOffset);
|
||
adjustedEndPoint = ApplyVerticalOffset(endPoint.Position, hostUp, verticalOffset);
|
||
}
|
||
else
|
||
{
|
||
// 其他情况,不调整起点和终点位置
|
||
adjustedStartPoint = startPoint.Position;
|
||
adjustedEndPoint = endPoint.Position;
|
||
}
|
||
|
||
// 🔥 通行空间包裹调整:水平段和层间垂直过渡延伸以完全包裹物体(仅通行空间)
|
||
if (_showObjectSpace)
|
||
{
|
||
if (!isVerticalSegment)
|
||
{
|
||
// 水平段:沿路径方向延伸
|
||
double alongPathSize = _passageAlongPath;
|
||
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||
{
|
||
alongPathSize = isTurn90Horizontal ? _passageAcrossPath : _objectLength;
|
||
}
|
||
ExtendLineSegmentForObjectSpace(ref adjustedStartPoint, ref adjustedEndPoint, alongPathSize);
|
||
}
|
||
else if (isVerticalSegment && !isFirstOrLastSegment)
|
||
{
|
||
// 层间垂直过渡:通行空间总高度 = 层间路径高度 + 物体高度
|
||
// 顶面中心 = 路径高点,底面中心 = 路径低点向下物体高度
|
||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||
bool isStartHigher =
|
||
HoistingCoordinateHelper.GetElevation(adjustedStartPoint, adapter) >
|
||
HoistingCoordinateHelper.GetElevation(adjustedEndPoint, adapter);
|
||
|
||
if (isStartHigher)
|
||
{
|
||
adjustedEndPoint = HoistingCoordinateHelper.ExtendVerticalTransitionForObjectSpace(
|
||
adjustedEndPoint,
|
||
_objectHeight,
|
||
isLowerPoint: true,
|
||
adapter: adapter);
|
||
}
|
||
else
|
||
{
|
||
adjustedStartPoint = HoistingCoordinateHelper.ExtendVerticalTransitionForObjectSpace(
|
||
adjustedStartPoint,
|
||
_objectHeight,
|
||
isLowerPoint: true,
|
||
adapter: adapter);
|
||
}
|
||
}
|
||
// 起吊/下降段不需要延伸,因为底面对齐地面
|
||
}
|
||
|
||
var lineMarker = new LineMarker
|
||
{
|
||
StartPoint = adjustedStartPoint,
|
||
EndPoint = adjustedEndPoint,
|
||
Color = lineColor,
|
||
Radius = GetLineRadius(),
|
||
SegmentType = PathSegmentType.Straight,
|
||
FromIndex = startPoint.Index,
|
||
ToIndex = endPoint.Index,
|
||
Height = height,
|
||
Width = width,
|
||
Opacity = lineOpacity,
|
||
HorizontalDirection = horizontalDirection, // 设置水平段方向向量
|
||
UpDirection = preferredUp
|
||
};
|
||
visualization.PathLineMarkers.Add(lineMarker);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算垂直偏移后的点
|
||
/// </summary>
|
||
/// <param name="point">原始点</param>
|
||
/// <param name="up">长方体的轴线方向(高度向量)</param>
|
||
/// <param name="verticalOffset">垂直偏移量</param>
|
||
/// <returns>偏移后的点</returns>
|
||
private Point3D ApplyVerticalOffset(Point3D point, Vector3D up, double verticalOffset)
|
||
{
|
||
if (Math.Abs(verticalOffset) < 0.001)
|
||
{
|
||
return point;
|
||
}
|
||
|
||
var normalizedUp = Normalize(up);
|
||
if (normalizedUp.X == 0.0 && normalizedUp.Y == 0.0 && normalizedUp.Z == 0.0)
|
||
{
|
||
return point;
|
||
}
|
||
|
||
return new Point3D(
|
||
point.X + normalizedUp.X * verticalOffset,
|
||
point.Y + normalizedUp.Y * verticalOffset,
|
||
point.Z + normalizedUp.Z * verticalOffset
|
||
);
|
||
}
|
||
|
||
private static System.Numerics.Vector3 ToVector3(Point3D point)
|
||
{
|
||
return new System.Numerics.Vector3((float)point.X, (float)point.Y, (float)point.Z);
|
||
}
|
||
|
||
private static Vector3D ToVector3D(System.Numerics.Vector3 vector)
|
||
{
|
||
return new Vector3D(vector.X, vector.Y, vector.Z);
|
||
}
|
||
|
||
private static Vector3D GetHostUpVector()
|
||
{
|
||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||
return Normalize(adapter.FromCanonicalVector(HostCoordinateAdapter.CanonicalUp));
|
||
}
|
||
|
||
private static Vector3D Cross(Vector3D a, Vector3D b)
|
||
{
|
||
return new Vector3D(
|
||
a.Y * b.Z - a.Z * b.Y,
|
||
a.Z * b.X - a.X * b.Z,
|
||
a.X * b.Y - a.Y * b.X);
|
||
}
|
||
|
||
private static Vector3D Normalize(Vector3D vector)
|
||
{
|
||
double lengthSquared = vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z;
|
||
if (lengthSquared < 1e-9)
|
||
{
|
||
return new Vector3D(0, 0, 0);
|
||
}
|
||
|
||
double length = Math.Sqrt(lengthSquared);
|
||
return new Vector3D(vector.X / length, vector.Y / length, vector.Z / length);
|
||
}
|
||
|
||
private static Point3D ApplyVectorOffset(Point3D point, Vector3D direction, double distance)
|
||
{
|
||
var normalized = Normalize(direction);
|
||
return new Point3D(
|
||
point.X + normalized.X * distance,
|
||
point.Y + normalized.Y * distance,
|
||
point.Z + normalized.Z * distance);
|
||
}
|
||
|
||
private static (Vector3D axis1, Vector3D axis2) GetHostPlanarAxes(Vector3D hostUp)
|
||
{
|
||
var normalizedUp = Normalize(hostUp);
|
||
var reference = Math.Abs(normalizedUp.X) > 0.9
|
||
? new Vector3D(0, 1, 0)
|
||
: new Vector3D(1, 0, 0);
|
||
|
||
double projection = reference.X * normalizedUp.X + reference.Y * normalizedUp.Y + reference.Z * normalizedUp.Z;
|
||
var axis1 = Normalize(new Vector3D(
|
||
reference.X - projection * normalizedUp.X,
|
||
reference.Y - projection * normalizedUp.Y,
|
||
reference.Z - projection * normalizedUp.Z));
|
||
|
||
if (axis1.X == 0 && axis1.Y == 0 && axis1.Z == 0)
|
||
{
|
||
axis1 = new Vector3D(0, 0, 1);
|
||
}
|
||
|
||
var axis2 = Normalize(Cross(normalizedUp, axis1));
|
||
return (axis1, axis2);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加切点标记
|
||
/// </summary>
|
||
/// <param name="visualization">路径可视化对象</param>
|
||
/// <param name="edge">路径边</param>
|
||
private void AddTangentMarkers(PathVisualization visualization, PathEdge edge)
|
||
{
|
||
if (edge.Trajectory == null) return;
|
||
|
||
// 使用统一样式
|
||
var tangentStyle = GetRenderStyle(RenderStyleName.TangentPoint);
|
||
var tangentColor = tangentStyle.Color;
|
||
|
||
// 路径宽度基准值(柱状和带状都是 GetLineRadius() * 2)
|
||
var pathWidth = GetLineRadius() * 2;
|
||
|
||
// 切点半径:路径半径的一半(切点直径 = 路径宽度的一半)
|
||
var tangentRadius = GetLineRadius() * 0.5;
|
||
|
||
// 切点高度:路径宽度的一半
|
||
var tangentHeight = pathWidth * 0.5;
|
||
|
||
// 进入切点Ts
|
||
var tsMarker = new SquareMarker
|
||
{
|
||
Center = edge.Trajectory.Ts,
|
||
Normal = GetHostUpVector(),
|
||
Size = tangentRadius * 2, // 直径
|
||
Height = tangentHeight, // 高度
|
||
Color = tangentColor,
|
||
Alpha = 0.8, // 80%透明度
|
||
EdgeId = edge.Id,
|
||
TangentType = TangentPointType.EntryTangent
|
||
};
|
||
visualization.TangentMarkers.Add(tsMarker);
|
||
|
||
// 退出切点Te
|
||
var teMarker = new SquareMarker
|
||
{
|
||
Center = edge.Trajectory.Te,
|
||
Normal = GetHostUpVector(),
|
||
Size = tangentRadius * 2, // 直径
|
||
Height = tangentHeight, // 高度
|
||
Color = tangentColor,
|
||
Alpha = 0.8, // 80%透明度
|
||
EdgeId = edge.Id,
|
||
TangentType = TangentPointType.ExitTangent
|
||
};
|
||
visualization.TangentMarkers.Add(teMarker);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建物体通行空间标记
|
||
/// </summary>
|
||
/// <param name="fromPoint">起点</param>
|
||
/// <param name="toPoint">终点</param>
|
||
/// <returns>物体通行空间标记</returns>
|
||
private ObjectSpaceMarker CreateObjectSpaceMarker(PathPoint fromPoint, PathPoint toPoint)
|
||
{
|
||
var style = GetRenderStyle(RenderStyleName.ObjectSpace);
|
||
return new ObjectSpaceMarker
|
||
{
|
||
StartPoint = fromPoint.Position,
|
||
EndPoint = toPoint.Position,
|
||
Width = _passageAcrossPath, // 直接使用模型单位
|
||
Height = _passageNormalToPath, // 直接使用模型单位
|
||
Color = style.Color, // 物体通行空间颜色
|
||
Alpha = style.Alpha, // 透明度,统一管理
|
||
FromIndex = fromPoint.Index,
|
||
ToIndex = toPoint.Index
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建点标记
|
||
/// </summary>
|
||
/// <param name="point">路径点</param>
|
||
/// <returns>圆形标记</returns>
|
||
private CircleMarker CreatePointMarker(PathPoint point)
|
||
{
|
||
// 检查是否是网格/体素可视化点(通过名称判断)
|
||
bool isGridVisualization = point.Name.StartsWith("网格(") || point.Name.StartsWith("网格_") ||
|
||
point.Name.StartsWith("体素("); // 体素点
|
||
|
||
// 确定网格点样式(颜色+透明度)
|
||
RenderStyle gridStyle = new RenderStyle(Color.White, 1.0); // 默认样式
|
||
if (isGridVisualization)
|
||
{
|
||
// 根据网格类型确定样式
|
||
if (point.Name.Contains("空洞"))
|
||
{
|
||
gridStyle = GetRenderStyle(RenderStyleName.GridUnknown);
|
||
}
|
||
else if (point.Name.Contains("障碍") || point.Name.Contains("Obstacle"))
|
||
{
|
||
gridStyle = GetRenderStyle(RenderStyleName.GridObstacle);
|
||
}
|
||
else if (point.Name.Contains("通道") || point.Name.Contains("Channel") || point.Name.Contains("开放"))
|
||
{
|
||
gridStyle = GetRenderStyle(RenderStyleName.GridChannel);
|
||
}
|
||
// 也可以通过Notes字段检查
|
||
else if (!string.IsNullOrEmpty(point.Notes) && point.Notes.StartsWith("GridType:"))
|
||
{
|
||
var gridTypeStr = point.Notes.Substring(9); // 去掉"GridType:"前缀
|
||
if (gridTypeStr == "空洞")
|
||
gridStyle = GetRenderStyle(RenderStyleName.GridUnknown);
|
||
else if (gridTypeStr == "障碍物")
|
||
gridStyle = GetRenderStyle(RenderStyleName.GridObstacle);
|
||
else
|
||
gridStyle = GetRenderStyle(RenderStyleName.GridChannel);
|
||
}
|
||
|
||
// 门网格点使用50%透明度覆盖默认透明度
|
||
if (point.Name.Contains("门"))
|
||
{
|
||
gridStyle = new RenderStyle(gridStyle.Color, 0.5);
|
||
}
|
||
}
|
||
|
||
return new CircleMarker
|
||
{
|
||
Center = point.Position,
|
||
Normal = GetHostUpVector(),
|
||
Radius = point.ResolveVisualizationRadius(
|
||
isGridVisualization ? GetRadiusForGridVisualization() : GetRadiusForPointType(point.Type)),
|
||
Color = isGridVisualization ? gridStyle.Color : GetColorForPointType(point.Type),
|
||
Alpha = isGridVisualization ? gridStyle.Alpha : 1.0,
|
||
Filled = true,
|
||
PointType = point.Type,
|
||
GridPointType = isGridVisualization ? _gridPointType : GridPointType.Sphere, // 网格点使用配置的类型,其他点使用球形
|
||
SequenceNumber = point.Index, // 使用Index而不是任意序号
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取网格可视化小球的半径(比正常路径点小)
|
||
/// </summary>
|
||
/// <returns>网格可视化半径</returns>
|
||
private double GetRadiusForGridVisualization()
|
||
{
|
||
// 使用标准尺寸的1/5作为网格点尺寸
|
||
double standardRadiusInMeters = GetStandardRadiusInMeters();
|
||
double radiusInMeters = standardRadiusInMeters / 5.0;
|
||
|
||
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||
return radiusInMeters * metersToModelUnits;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置当前网格大小(米),用于自适应点大小计算
|
||
/// </summary>
|
||
/// <param name="gridSizeInMeters">网格大小(米)</param>
|
||
public void SetGridSize(double gridSizeInMeters)
|
||
{
|
||
_currentGridSizeInMeters = gridSizeInMeters;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置物体参数(从PathPlanningManager同步)
|
||
/// </summary>
|
||
/// <summary>
|
||
/// 设置通行空间参数 - 外部传入模型单位,内部直接使用
|
||
/// </summary>
|
||
/// <param name="passageAcrossPath">垂直于路径方向的通行空间尺寸(模型单位)</param>
|
||
/// <param name="passageNormalToPath">法线方向的通行空间尺寸(模型单位)</param>
|
||
/// <param name="passageAlongPath">沿路径方向的通行空间尺寸(模型单位)</param>
|
||
/// <param name="passageNormalToPathVertical">垂直段的高度(物体长度 + 2×安全间隙,模型单位)</param>
|
||
/// <param name="passageNormalToPathHorizontal">水平段的高度(物体高度 + 2×安全间隙,模型单位)</param>
|
||
public void SetPassageSpaceParameters(
|
||
double passageAcrossPath,
|
||
double passageNormalToPath,
|
||
double passageAlongPath,
|
||
double passageNormalToPathVertical,
|
||
double passageNormalToPathHorizontal)
|
||
{
|
||
// 直接存储模型单位(调用方已转换)
|
||
_passageAlongPath = passageAlongPath;
|
||
_passageAcrossPath = passageAcrossPath;
|
||
_passageNormalToPath = passageNormalToPath;
|
||
|
||
// 保存垂直段和水平段的高度(用于吊装路径分段渲染)
|
||
_objectLength = passageNormalToPathVertical;
|
||
_objectHeight = passageNormalToPathHorizontal;
|
||
|
||
// 参数改变时刷新所有普通路径(因为RibbonLine模式也使用物体宽度)
|
||
RefreshNormalPaths();
|
||
}
|
||
|
||
internal static double ResolveGroundPathObjectSpaceVerticalOffset(double objectSpaceHeight)
|
||
{
|
||
return objectSpaceHeight / 2.0;
|
||
}
|
||
|
||
#region 颜色管理
|
||
|
||
/// <summary>
|
||
/// 根据样式名称获取对应的渲染样式(颜色+透明度)
|
||
/// </summary>
|
||
/// <param name="styleName">样式名称</param>
|
||
/// <returns>对应的渲染样式</returns>
|
||
private RenderStyle GetRenderStyle(RenderStyleName styleName)
|
||
{
|
||
switch (styleName)
|
||
{
|
||
case RenderStyleName.StartPoint:
|
||
return new RenderStyle(Color.FromByteRGB(76, 175, 80), 0.9); // Material Green起点,10%透明
|
||
|
||
case RenderStyleName.EndPoint:
|
||
return new RenderStyle(Color.FromByteRGB(244, 67, 54), 0.9); // Material Red终点,10%透明
|
||
|
||
case RenderStyleName.WayPoint:
|
||
return new RenderStyle(Color.FromByteRGB(33, 150, 243), 0.9); // Material Blue路径点,10%透明
|
||
|
||
case RenderStyleName.Line:
|
||
return new RenderStyle(Color.FromByteRGB(255, 152, 0), 0.8); // Material Orange连线,20%透明
|
||
|
||
case RenderStyleName.ObjectSpace:
|
||
{
|
||
// 从配置读取通行空间透明度,默认为0.4(40%不透明,60%透明)
|
||
double opacity = ConfigManager.Instance.Current.Visualization.PassageSpaceOpacity;
|
||
// 确保透明度在有效范围内
|
||
opacity = Math.Max(0.0, Math.Min(1.0, opacity));
|
||
return new RenderStyle(Color.FromByteRGB(129, 199, 132), opacity); // Material Light Green通行空间
|
||
}
|
||
case RenderStyleName.PreviewPoint:
|
||
return new RenderStyle(Color.White, 0.7); // 白色预览点,30%透明
|
||
|
||
case RenderStyleName.PreviewLine:
|
||
return new RenderStyle(Color.FromByteRGB(128, 128, 128), 0.5); // 灰色预览连线,50%透明
|
||
|
||
case RenderStyleName.UnreachedEndPoint:
|
||
return new RenderStyle(Color.FromByteRGB(139, 0, 0), 0.7); // 深红色未到达终点,30%透明
|
||
|
||
case RenderStyleName.GridChannel:
|
||
return new RenderStyle(Color.FromByteRGB(129, 199, 132), 0.8); // Material Light Green网格通道,20%透明
|
||
|
||
case RenderStyleName.GridObstacle:
|
||
return new RenderStyle(Color.FromByteRGB(117, 117, 117), 0.8); // Material Grey网格障碍物,20%透明
|
||
|
||
case RenderStyleName.GridUnknown:
|
||
return new RenderStyle(Color.FromByteRGB(255, 112, 67), 0.8); // Material Deep Orange网格未知区域,20%透明
|
||
|
||
case RenderStyleName.TangentPoint:
|
||
return new RenderStyle(Color.FromByteRGB(244, 67, 54), 0.8); // Material Red切点,20%透明
|
||
|
||
case RenderStyleName.SafetyWarning:
|
||
return new RenderStyle(Color.FromByteRGB(244, 67, 54), 0.85); // Material Red安全警告,15%透明
|
||
|
||
case RenderStyleName.RailBaseline:
|
||
return new RenderStyle(Color.FromByteRGB(255, 138, 128), 0.7); // 浅红色,30%透明
|
||
|
||
case RenderStyleName.AssemblyGuideLine:
|
||
return new RenderStyle(Color.FromByteRGB(76, 175, 80), 0.85); // Material Green装配基准向量
|
||
|
||
case RenderStyleName.AssemblyInstallationPlane:
|
||
return new RenderStyle(Color.FromByteRGB(76, 175, 80), 0.5); // Material Green安装参考面,50%透明
|
||
|
||
case RenderStyleName.HoistingLine:
|
||
return new RenderStyle(Color.FromByteRGB(156, 39, 176), 0.8); // Material Purple吊装路径,20%透明
|
||
|
||
case RenderStyleName.TransformAxisX:
|
||
return new RenderStyle(Color.FromByteRGB(244, 67, 54), 0.9); // Material Red
|
||
|
||
case RenderStyleName.TransformAxisY:
|
||
return new RenderStyle(Color.FromByteRGB(76, 175, 80), 0.9); // Material Green
|
||
|
||
case RenderStyleName.TransformAxisZ:
|
||
return new RenderStyle(Color.FromByteRGB(33, 150, 243), 0.9); // Material Blue
|
||
|
||
default:
|
||
return new RenderStyle(Color.White, 1.0); // 默认白色,完全不透明
|
||
}
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 获取连线半径
|
||
/// </summary>
|
||
/// <returns>连线半径</returns>
|
||
private double GetLineRadius()
|
||
{
|
||
// 获取标准尺寸(起点尺寸)
|
||
double standardRadiusInMeters = GetStandardRadiusInMeters();
|
||
|
||
// 连线半径为标准尺寸的40%
|
||
double lineRadiusInMeters = standardRadiusInMeters * 0.4;
|
||
|
||
return lineRadiusInMeters * UnitsConverter.GetMetersToUnitsConversionFactor();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取路径可视化对象
|
||
/// </summary>
|
||
/// <param name="pathId">路径ID</param>
|
||
/// <returns>路径可视化对象,如果不存在返回null</returns>
|
||
private PathVisualization GetPathVisualization(string pathId)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
_pathVisualizations.TryGetValue(pathId, out var visualization);
|
||
return visualization;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 渲染预览点(灰色显示)
|
||
/// </summary>
|
||
/// <param name="previewPoint">预览点对象</param>
|
||
public void RenderPreviewPoint(PathPoint previewPoint)
|
||
{
|
||
try
|
||
{
|
||
if (previewPoint == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
// 获取适当的半径
|
||
double radius = GetRadiusForPointType(previewPoint.Type);
|
||
|
||
// 获取预览点样式(颜色+透明度)
|
||
var previewStyle = GetRenderStyle(RenderStyleName.PreviewPoint);
|
||
|
||
// 创建预览点标记
|
||
_previewMarker = new CircleMarker
|
||
{
|
||
Position = previewPoint.Position,
|
||
Radius = radius,
|
||
Color = previewStyle.Color,
|
||
Alpha = previewStyle.Alpha,
|
||
IsVisible = true,
|
||
PathPoint = previewPoint
|
||
};
|
||
|
||
// 请求刷新视图
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[预览点渲染] 渲染预览点失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除预览点
|
||
/// </summary>
|
||
public void ClearPreviewPoint()
|
||
{
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (_previewMarker != null || _previewLines.Count > 0)
|
||
{
|
||
_previewMarker = null;
|
||
_previewLines.Clear();
|
||
|
||
// 请求刷新视图
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[预览点渲染] 清除预览点失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染预览连线
|
||
/// </summary>
|
||
/// <param name="previewPoint">预览点</param>
|
||
/// <param name="pathPoints">当前路径点列表</param>
|
||
public void RenderPreviewLines(PathPoint previewPoint, List<PathPoint> pathPoints)
|
||
{
|
||
try
|
||
{
|
||
if (previewPoint == null || pathPoints == null || pathPoints.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
// 清除旧的预览连线
|
||
_previewLines.Clear();
|
||
|
||
// 找到预览点应该插入的最近线段
|
||
var nearestSegment = FindNearestLineSegment(previewPoint.Position, pathPoints);
|
||
|
||
if (nearestSegment.HasValue)
|
||
{
|
||
var (prevPoint, nextPoint) = nearestSegment.Value;
|
||
|
||
// 创建两条预览连线
|
||
// 1. 前一个点 -> 预览点
|
||
var line1 = new LineMarker
|
||
{
|
||
StartPoint = prevPoint.Position,
|
||
EndPoint = previewPoint.Position,
|
||
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, // 预览连线颜色
|
||
Radius = GetLineRadius() // 使用与正常连线相同的直径
|
||
};
|
||
_previewLines.Add(line1);
|
||
|
||
// 2. 预览点 -> 后一个点
|
||
var line2 = new LineMarker
|
||
{
|
||
StartPoint = previewPoint.Position,
|
||
EndPoint = nextPoint.Position,
|
||
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, // 预览连线颜色
|
||
Radius = GetLineRadius()
|
||
};
|
||
_previewLines.Add(line2);
|
||
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[预览连线渲染] 未找到合适的线段插入预览点");
|
||
}
|
||
|
||
// 请求刷新视图
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[预览连线渲染] 渲染预览连线失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除预览连线
|
||
/// </summary>
|
||
public void ClearPreviewLines()
|
||
{
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (_previewLines.Count > 0)
|
||
{
|
||
_previewLines.Clear();
|
||
|
||
// 请求刷新视图
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[预览连线渲染] 清除预览连线失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染预览路径(用于修改路径点时的预览)
|
||
/// </summary>
|
||
/// <param name="previewRoute">包含预览修改的路径</param>
|
||
public void RenderPreviewPath(PathRoute previewRoute)
|
||
{
|
||
try
|
||
{
|
||
if (previewRoute == null || previewRoute.Points.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
// 只清理预览元素,不影响原有路径
|
||
ClearPreviewPoint();
|
||
ClearPreviewLines();
|
||
|
||
// 找到预览点及其索引
|
||
var previewPointIndex = -1;
|
||
PathPoint previewPoint = null;
|
||
|
||
for (int i = 0; i < previewRoute.Points.Count; i++)
|
||
{
|
||
if (previewRoute.Points[i].Name.Contains("_预览"))
|
||
{
|
||
previewPointIndex = i;
|
||
previewPoint = previewRoute.Points[i];
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (previewPoint != null && previewPointIndex >= 0)
|
||
{
|
||
// 只渲染预览点
|
||
RenderPreviewPoint(previewPoint);
|
||
|
||
// 只渲染与预览点相关的连线
|
||
// 连接前一个点到预览点
|
||
if (previewPointIndex > 0)
|
||
{
|
||
var prevPoint = previewRoute.Points[previewPointIndex - 1];
|
||
var line1 = new LineMarker
|
||
{
|
||
StartPoint = prevPoint.Position,
|
||
EndPoint = previewPoint.Position,
|
||
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, // 预览连线颜色
|
||
Radius = GetLineRadius()
|
||
};
|
||
_previewLines.Add(line1);
|
||
}
|
||
|
||
// 连接预览点到下一个点
|
||
if (previewPointIndex < previewRoute.Points.Count - 1)
|
||
{
|
||
var nextPoint = previewRoute.Points[previewPointIndex + 1];
|
||
var line2 = new LineMarker
|
||
{
|
||
StartPoint = previewPoint.Position,
|
||
EndPoint = nextPoint.Position,
|
||
Color = GetRenderStyle(RenderStyleName.PreviewLine).Color, // 预览连线颜色
|
||
Radius = GetLineRadius()
|
||
};
|
||
_previewLines.Add(line2);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[预览路径渲染] 未找到预览点,跳过渲染");
|
||
}
|
||
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[预览路径渲染] 渲染预览路径失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理所有预览渲染
|
||
/// </summary>
|
||
public void ClearPreview()
|
||
{
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
// 清理预览点
|
||
ClearPreviewPoint();
|
||
|
||
// 清理预览连线
|
||
ClearPreviewLines();
|
||
|
||
RequestViewRefresh();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[清理预览] 清理预览失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
#region 私有辅助方法
|
||
|
||
/// <returns>标准尺寸(米)</returns>
|
||
/// <summary>
|
||
/// 计算标准尺寸(起点尺寸),其他元素以此为基准按比例计算
|
||
/// </summary>
|
||
/// <returns>标准尺寸(米)</returns>
|
||
private double GetStandardRadiusInMeters()
|
||
{
|
||
// 起点尺寸为网格大小的100%,并限制在合理范围内
|
||
double standardRadius = _currentGridSizeInMeters * 1.0;
|
||
|
||
// 边界限制:最小0.1米,最大0.5米
|
||
return Math.Max(0.1, Math.Min(0.5, standardRadius));
|
||
}
|
||
|
||
public double GetRadiusForPointType(PathPointType pointType)
|
||
{
|
||
// 获取标准尺寸(起点尺寸)
|
||
double standardRadiusInMeters = GetStandardRadiusInMeters();
|
||
|
||
// 根据点类型应用比例系数
|
||
double baseRadiusInMeters;
|
||
if (pointType == PathPointType.WayPoint)
|
||
{
|
||
// 路径点为标准尺寸的1/4
|
||
baseRadiusInMeters = standardRadiusInMeters * 0.25;
|
||
}
|
||
else
|
||
{
|
||
// 起点/终点使用标准尺寸的1/2
|
||
baseRadiusInMeters = standardRadiusInMeters * 0.5;
|
||
}
|
||
|
||
// 获取真实文档单位转换系数
|
||
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||
|
||
// 转换为模型单位
|
||
double radius = baseRadiusInMeters * metersToModelUnits;
|
||
|
||
return radius;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据路径点类型获取颜色
|
||
/// </summary>
|
||
public Color GetColorForPointType(PathPointType pointType)
|
||
{
|
||
switch (pointType)
|
||
{
|
||
case PathPointType.StartPoint:
|
||
return GetRenderStyle(RenderStyleName.StartPoint).Color; // 起点绿色
|
||
case PathPointType.EndPoint:
|
||
return GetRenderStyle(RenderStyleName.EndPoint).Color; // 终点红色
|
||
case PathPointType.WayPoint:
|
||
default:
|
||
return GetRenderStyle(RenderStyleName.WayPoint).Color; // 路径中间点蓝色
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算两点间距离
|
||
/// </summary>
|
||
private double CalculateDistance(Point3D point1, Point3D point2)
|
||
{
|
||
var dx = point1.X - point2.X;
|
||
var dy = point1.Y - point2.Y;
|
||
var dz = point1.Z - point2.Z;
|
||
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染长方体标记(通用方法)
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="startPoint">起点</param>
|
||
/// <param name="endPoint">终点</param>
|
||
/// <param name="width">宽度</param>
|
||
/// <param name="height">高度</param>
|
||
/// <param name="horizontalDirection">水平段方向向量(用于垂直路径渲染时确定通行空间方向)</param>
|
||
private void RenderCuboidMarker(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, Vector3D horizontalDirection = null, Vector3D upReference = null)
|
||
{
|
||
try
|
||
{
|
||
// 计算方向向量
|
||
var direction = new Vector3D(
|
||
endPoint.X - startPoint.X,
|
||
endPoint.Y - startPoint.Y,
|
||
endPoint.Z - startPoint.Z
|
||
);
|
||
|
||
// 计算长度
|
||
var length = Math.Sqrt(direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z);
|
||
|
||
if (length < 0.001) // 避免零长度线段
|
||
return;
|
||
|
||
// 归一化方向向量
|
||
direction = new Vector3D(direction.X / length, direction.Y / length, direction.Z / length);
|
||
|
||
var (right, up) = ObjectSpaceOrientationHelper.CalculateAxes(
|
||
startPoint,
|
||
endPoint,
|
||
upReference ?? GetHostUpVector(),
|
||
horizontalDirection);
|
||
|
||
// 计算长方体的边界框和向量
|
||
var halfWidth = width / 2.0;
|
||
var halfHeight = height / 2.0;
|
||
|
||
// 计算立方体原点(使长方体中心在起点位置)
|
||
var origin = new Point3D(
|
||
startPoint.X - halfWidth * right.X - halfHeight * up.X,
|
||
startPoint.Y - halfWidth * right.Y - halfHeight * up.Y,
|
||
startPoint.Z - halfWidth * right.Z - halfHeight * up.Z
|
||
);
|
||
|
||
// 定义立方体的三个轴向量
|
||
var xVector = new Vector3D(direction.X * length, direction.Y * length, direction.Z * length); // 长度方向(沿路径)
|
||
var yVector = new Vector3D(right.X * width, right.Y * width, right.Z * width); // 宽度方向(垂直于路径)
|
||
var zVector = new Vector3D(up.X * height, up.Y * height, up.Z * height); // 高度方向(垂直于路径和宽度)
|
||
|
||
// 使用Cuboid API渲染长方体
|
||
graphics.Cuboid(origin, xVector, yVector, zVector, true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[长方体渲染] 渲染长方体失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染长方体标记(指定长度)
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="startPoint">起点</param>
|
||
/// <param name="endPoint">终点</param>
|
||
/// <param name="width">宽度</param>
|
||
/// <param name="height">高度</param>
|
||
/// <param name="length">指定长度(覆盖默认计算值)</param>
|
||
/// <param name="horizontalDirection">水平段方向向量(用于垂直路径渲染时确定通行空间方向)</param>
|
||
private void RenderCuboidMarkerWithLength(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, double length, Vector3D horizontalDirection = null, Vector3D upReference = null)
|
||
{
|
||
try
|
||
{
|
||
// 计算方向向量
|
||
var direction = new Vector3D(
|
||
endPoint.X - startPoint.X,
|
||
endPoint.Y - startPoint.Y,
|
||
endPoint.Z - startPoint.Z
|
||
);
|
||
|
||
// 归一化方向向量
|
||
var directionLength = Math.Sqrt(direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z);
|
||
if (directionLength < 0.001) // 避免零长度线段
|
||
return;
|
||
|
||
direction = new Vector3D(direction.X / directionLength, direction.Y / directionLength, direction.Z / directionLength);
|
||
|
||
var (right, up) = ObjectSpaceOrientationHelper.CalculateAxes(
|
||
startPoint,
|
||
endPoint,
|
||
upReference ?? GetHostUpVector(),
|
||
horizontalDirection);
|
||
|
||
// 计算长方体的边界框和向量
|
||
var halfWidth = width / 2.0;
|
||
var halfHeight = height / 2.0;
|
||
|
||
// 计算立方体原点(使长方体中心在起点位置)
|
||
var origin = new Point3D(
|
||
startPoint.X - halfWidth * right.X - halfHeight * up.X,
|
||
startPoint.Y - halfWidth * right.Y - halfHeight * up.Y,
|
||
startPoint.Z - halfWidth * right.Z - halfHeight * up.Z
|
||
);
|
||
|
||
// 定义立方体的三个轴向量
|
||
var xVector = new Vector3D(direction.X * length, direction.Y * length, direction.Z * length); // 使用指定长度(沿路径)
|
||
var yVector = new Vector3D(right.X * width, right.Y * width, right.Z * width); // 宽度方向(垂直于路径)
|
||
var zVector = new Vector3D(up.X * height, up.Y * height, up.Z * height); // 高度方向(垂直于路径和宽度)
|
||
|
||
// 使用Cuboid API渲染长方体
|
||
graphics.Cuboid(origin, xVector, yVector, zVector, true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[长方体渲染] 渲染长方体失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染带状连线(长方体)
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="lineMarker">连线标记</param>
|
||
private void RenderRibbonLineMarker(Graphics graphics, LineMarker lineMarker)
|
||
{
|
||
// 使用 LineMarker 的 Width 属性(如果没有设置则使用 Radius * 2)
|
||
var width = lineMarker.Width > 0 ? lineMarker.Width : lineMarker.Radius * 2;
|
||
|
||
// 检查是否是圆弧段且有多段采样点
|
||
if (lineMarker.SegmentType == PathSegmentType.Arc &&
|
||
lineMarker.SampledPoints != null &&
|
||
lineMarker.SampledPoints.Count >= 2)
|
||
{
|
||
// 圆弧段:在每个采样点渲染完整的物体长方体
|
||
RenderArcSegmentCuboids(graphics, lineMarker, width);
|
||
}
|
||
else
|
||
{
|
||
// 直线段:单个长方体
|
||
// 设置颜色和透明度
|
||
graphics.Color(lineMarker.Color, lineMarker.Opacity);
|
||
|
||
RenderCuboidMarker(
|
||
graphics,
|
||
lineMarker.StartPoint,
|
||
lineMarker.EndPoint,
|
||
width,
|
||
lineMarker.Height,
|
||
lineMarker.HorizontalDirection,
|
||
lineMarker.UpDirection
|
||
);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染圆弧段的物体长方体
|
||
/// 在每个采样点处渲染一个完整的物体长方体,中心在采样点,向前后各延伸 L/2
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="lineMarker">连线标记(必须是圆弧段)</param>
|
||
/// <param name="width">长方体宽度</param>
|
||
private void RenderArcSegmentCuboids(Graphics graphics, LineMarker lineMarker, double width)
|
||
{
|
||
// 设置颜色和透明度
|
||
graphics.Color(lineMarker.Color, lineMarker.Opacity);
|
||
|
||
// 在每个采样点处渲染完整的物体长方体
|
||
for (int i = 0; i < lineMarker.SampledPoints.Count; i++)
|
||
{
|
||
// 计算当前采样点的切线方向
|
||
Vector3D tangent = CalculateTangentAtSamplePoint(lineMarker.SampledPoints, i);
|
||
|
||
// 计算长方体的起点和终点(沿切线方向,各延伸 L/2)
|
||
var halfLength = _passageAlongPath / 2.0;
|
||
var startPoint = new Point3D(
|
||
lineMarker.SampledPoints[i].X - tangent.X * halfLength,
|
||
lineMarker.SampledPoints[i].Y - tangent.Y * halfLength,
|
||
lineMarker.SampledPoints[i].Z - tangent.Z * halfLength
|
||
);
|
||
var endPoint = new Point3D(
|
||
lineMarker.SampledPoints[i].X + tangent.X * halfLength,
|
||
lineMarker.SampledPoints[i].Y + tangent.Y * halfLength,
|
||
lineMarker.SampledPoints[i].Z + tangent.Z * halfLength
|
||
);
|
||
|
||
RenderCuboidMarkerWithLength(
|
||
graphics,
|
||
startPoint,
|
||
endPoint,
|
||
width,
|
||
lineMarker.Height,
|
||
_passageAlongPath,
|
||
lineMarker.HorizontalDirection,
|
||
lineMarker.UpDirection
|
||
);
|
||
}
|
||
}
|
||
|
||
private Vector3D ResolveObjectSpaceUpDirection(PathRoute route)
|
||
{
|
||
if (route?.PathType == NavisworksTransport.PathType.Rail && route.RailPreferredNormal != null)
|
||
{
|
||
return Normalize(new Vector3D(
|
||
route.RailPreferredNormal.X,
|
||
route.RailPreferredNormal.Y,
|
||
route.RailPreferredNormal.Z));
|
||
}
|
||
|
||
return GetHostUpVector();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算采样点处的切线方向
|
||
/// </summary>
|
||
/// <param name="sampledPoints">采样点列表</param>
|
||
/// <param name="index">当前采样点索引</param>
|
||
/// <returns>归一化的切线向量</returns>
|
||
private Vector3D CalculateTangentAtSamplePoint(List<Point3D> sampledPoints, int index)
|
||
{
|
||
Vector3D tangent;
|
||
|
||
if (index == 0)
|
||
{
|
||
// 第一个点:使用到下一点的向量作为切线
|
||
tangent = new Vector3D(
|
||
sampledPoints[1].X - sampledPoints[0].X,
|
||
sampledPoints[1].Y - sampledPoints[0].Y,
|
||
sampledPoints[1].Z - sampledPoints[0].Z
|
||
);
|
||
}
|
||
else if (index == sampledPoints.Count - 1)
|
||
{
|
||
// 最后一个点:使用从上一点到当前点的向量作为切线
|
||
tangent = new Vector3D(
|
||
sampledPoints[index].X - sampledPoints[index - 1].X,
|
||
sampledPoints[index].Y - sampledPoints[index - 1].Y,
|
||
sampledPoints[index].Z - sampledPoints[index - 1].Z
|
||
);
|
||
}
|
||
else
|
||
{
|
||
// 中间点:使用前后两点的平均方向作为切线
|
||
var prev = new Vector3D(
|
||
sampledPoints[index].X - sampledPoints[index - 1].X,
|
||
sampledPoints[index].Y - sampledPoints[index - 1].Y,
|
||
sampledPoints[index].Z - sampledPoints[index - 1].Z
|
||
);
|
||
var next = new Vector3D(
|
||
sampledPoints[index + 1].X - sampledPoints[index].X,
|
||
sampledPoints[index + 1].Y - sampledPoints[index].Y,
|
||
sampledPoints[index + 1].Z - sampledPoints[index].Z
|
||
);
|
||
tangent = new Vector3D(prev.X + next.X, prev.Y + next.Y, prev.Z + next.Z);
|
||
}
|
||
|
||
// 归一化切线向量
|
||
var tangentLength = Math.Sqrt(tangent.X * tangent.X + tangent.Y * tangent.Y + tangent.Z * tangent.Z);
|
||
if (tangentLength > 0.001)
|
||
{
|
||
tangent = new Vector3D(tangent.X / tangentLength, tangent.Y / tangentLength, tangent.Z / tangentLength);
|
||
}
|
||
|
||
return tangent;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 渲染点标记
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="pointMarker">点标记</param>
|
||
private void RenderPointMarker(Graphics graphics, CircleMarker pointMarker)
|
||
{
|
||
switch (pointMarker.GridPointType)
|
||
{
|
||
case GridPointType.Rectangle:
|
||
// 渲染立方体
|
||
RenderCubeMarker(graphics, pointMarker);
|
||
break;
|
||
case GridPointType.Circle:
|
||
// 渲染圆
|
||
RenderCircleMarker(graphics, pointMarker);
|
||
break;
|
||
case GridPointType.Sphere:
|
||
default:
|
||
// 渲染球形(默认)
|
||
graphics.Sphere(pointMarker.Center, pointMarker.Radius);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染立方体标记
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="pointMarker">点标记</param>
|
||
private void RenderCubeMarker(Graphics graphics, CircleMarker pointMarker)
|
||
{
|
||
// 计算正方形的边长(直径)
|
||
double sideLength = pointMarker.Radius * 2;
|
||
|
||
// 网格矩形必须落在宿主水平平面内,不能写死世界 XY。
|
||
var hostUp = GetHostUpVector();
|
||
var (planarAxis1, planarAxis2) = GetHostPlanarAxes(hostUp);
|
||
var xVector = new Vector3D(planarAxis1.X * sideLength, planarAxis1.Y * sideLength, planarAxis1.Z * sideLength);
|
||
var yVector = new Vector3D(planarAxis2.X * sideLength, planarAxis2.Y * sideLength, planarAxis2.Z * sideLength);
|
||
|
||
// 沿宿主 up 略微抬高,避免与模型重叠。
|
||
var liftedCenter = ApplyVectorOffset(pointMarker.Center, hostUp, 0.01);
|
||
var origin = new Point3D(
|
||
liftedCenter.X - planarAxis1.X * pointMarker.Radius - planarAxis2.X * pointMarker.Radius,
|
||
liftedCenter.Y - planarAxis1.Y * pointMarker.Radius - planarAxis2.Y * pointMarker.Radius,
|
||
liftedCenter.Z - planarAxis1.Z * pointMarker.Radius - planarAxis2.Z * pointMarker.Radius
|
||
);
|
||
|
||
// 使用Rectangle API渲染正方形
|
||
graphics.Rectangle(origin, xVector, yVector, pointMarker.Filled);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染圆形标记
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="pointMarker">点标记</param>
|
||
private void RenderCircleMarker(Graphics graphics, CircleMarker pointMarker)
|
||
{
|
||
// 调整中心点位置,沿宿主 up 略微抬高以避免与模型重叠
|
||
Point3D adjustCenterPoint = ApplyVectorOffset(pointMarker.Center, pointMarker.Normal, 0.01);
|
||
|
||
// 使用Circle API渲染圆形
|
||
graphics.Circle(adjustCenterPoint, pointMarker.Normal, pointMarker.Radius, true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染方块标记(切点)- 使用圆锥体
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="marker">方块标记</param>
|
||
private void RenderSquareMarker(Graphics graphics, SquareMarker marker)
|
||
{
|
||
try
|
||
{
|
||
// 计算圆锥体半径(底部半径)
|
||
var radius = marker.Size / 2.0;
|
||
|
||
// 计算圆锥体高度
|
||
var height = marker.Height;
|
||
|
||
// 计算圆锥体起点和终点(底面放在通道上)
|
||
var startPoint = new Point3D(
|
||
marker.Center.X,
|
||
marker.Center.Y,
|
||
marker.Center.Z // 底面Z坐标 = 中心Z坐标
|
||
);
|
||
|
||
var endPoint = new Point3D(
|
||
marker.Center.X,
|
||
marker.Center.Y,
|
||
marker.Center.Z + height // 顶面Z坐标 = 中心Z坐标 + 高度
|
||
);
|
||
|
||
// 使用统一样式
|
||
graphics.Color(marker.Color, marker.Alpha);
|
||
|
||
// 使用Cone API渲染圆锥体(底部半径 = radius,顶部半径 = 0)
|
||
graphics.Cone(startPoint, endPoint, radius, 0);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[切点渲染] 渲染切点失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
private void RenderPlaneMarker(Graphics graphics, PlaneMarker marker)
|
||
{
|
||
graphics.Color(marker.Color, marker.Alpha);
|
||
graphics.Rectangle(marker.Origin, marker.XVector, marker.YVector, marker.Filled);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染连线标记(支持直线段和圆弧段)
|
||
/// </summary>
|
||
/// <param name="graphics">图形上下文</param>
|
||
/// <param name="lineMarker">连线标记</param>
|
||
private void RenderLineMarker(Graphics graphics, LineMarker lineMarker)
|
||
{
|
||
// 🔥 路径线渲染逻辑:保持原有行为
|
||
// 注意:ShowObjectSpace 的通行空间使用 RenderRibbonLineMarker 单独渲染
|
||
if (_visualizationMode == PathVisualizationMode.ObjectSpace || _visualizationMode == PathVisualizationMode.RibbonLine)
|
||
{
|
||
// ObjectSpace 或 RibbonLine 模式:使用长方体渲染
|
||
RenderRibbonLineMarker(graphics, lineMarker);
|
||
}
|
||
else
|
||
{
|
||
// StandardLine 模式:使用圆柱体渲染
|
||
var style = GetRenderStyle(RenderStyleName.Line);
|
||
graphics.Color(lineMarker.Color, style.Alpha);
|
||
if (lineMarker.SegmentType == PathSegmentType.Arc &&
|
||
lineMarker.SampledPoints != null &&
|
||
lineMarker.SampledPoints.Count >= 2)
|
||
{
|
||
// 圆弧段:多段圆柱体
|
||
// 为了避免外侧出现缝隙,需要根据外侧弧线长度调整每个圆柱体的厚度(半径)
|
||
double cylinderRadius = lineMarker.Radius; // 默认使用原始半径
|
||
|
||
if (lineMarker.Trajectory != null)
|
||
{
|
||
// 计算外侧弧线长度 = (圆弧半径 + 连线半径) * 偏转角
|
||
double outerArcLength = (lineMarker.Trajectory.ActualRadius + lineMarker.Radius) * lineMarker.Trajectory.DeflectionAngle;
|
||
|
||
// 计算每个圆柱体在外侧应该覆盖的长度
|
||
int segmentCount = lineMarker.SampledPoints.Count - 1;
|
||
double outerSegmentLength = outerArcLength / segmentCount;
|
||
|
||
// 圆柱体的半径(厚度)应该等于外侧段长度的一半
|
||
// 这样圆柱体在外侧的覆盖长度 = 2 * radius = outerSegmentLength,正好填满外侧弧线
|
||
cylinderRadius = outerSegmentLength / 2.0;
|
||
}
|
||
|
||
for (int i = 0; i < lineMarker.SampledPoints.Count - 1; i++)
|
||
{
|
||
graphics.Cylinder(lineMarker.SampledPoints[i], lineMarker.SampledPoints[i + 1], cylinderRadius);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 直线段:单个圆柱体
|
||
graphics.Cylinder(lineMarker.StartPoint, lineMarker.EndPoint, lineMarker.Radius);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 延伸线段起点和终点以完全包裹物体(ObjectSpace模式)
|
||
/// </summary>
|
||
/// <param name="startPoint">起点(会被修改)</param>
|
||
/// <param name="endPoint">终点(会被修改)</param>
|
||
private void ExtendLineSegmentForObjectSpace(ref Point3D startPoint, ref Point3D endPoint, double alongPathSize)
|
||
{
|
||
// 注意:调用此方法前应该检查 _showObjectSpace
|
||
if (!_showObjectSpace)
|
||
return;
|
||
|
||
var segDx = endPoint.X - startPoint.X;
|
||
var segDy = endPoint.Y - startPoint.Y;
|
||
var segDz = endPoint.Z - startPoint.Z;
|
||
var segLength = Math.Sqrt(segDx * segDx + segDy * segDy + segDz * segDz);
|
||
|
||
if (segLength > 0.001)
|
||
{
|
||
double offset = alongPathSize / 2.0;
|
||
var dirX = segDx / segLength;
|
||
var dirY = segDy / segLength;
|
||
var dirZ = segDz / segLength;
|
||
|
||
// 起点向反方向延伸
|
||
startPoint = new Point3D(
|
||
startPoint.X - dirX * offset,
|
||
startPoint.Y - dirY * offset,
|
||
startPoint.Z - dirZ * offset
|
||
);
|
||
|
||
// 终点向正方向延伸
|
||
endPoint = new Point3D(
|
||
endPoint.X + dirX * offset,
|
||
endPoint.Y + dirY * offset,
|
||
endPoint.Z + dirZ * offset
|
||
);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 请求视图刷新(带防抖机制)
|
||
/// </summary>
|
||
private void RequestViewRefresh()
|
||
{
|
||
try
|
||
{
|
||
// 防抖机制:最小间隔50ms,避免过于频繁的Navisworks重绘
|
||
// 使用高精度Ticks避免毫秒精度不足的问题
|
||
long currentTicks = DateTime.Now.Ticks;
|
||
long elapsedTicks = currentTicks - _lastRefreshTicks;
|
||
long intervalMs = elapsedTicks / TimeSpan.TicksPerMillisecond;
|
||
|
||
if (intervalMs < 50)
|
||
{
|
||
return;
|
||
}
|
||
_lastRefreshTicks = currentTicks;
|
||
|
||
// 在程序关闭阶段,文档或视图可能为空,这是正常的,静默返回
|
||
if (Application.ActiveDocument?.ActiveView != null)
|
||
{
|
||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[视图刷新] 失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 找到预览点应该插入的最近线段
|
||
/// </summary>
|
||
/// <param name="previewPosition">预览点位置</param>
|
||
/// <param name="pathPoints">路径点列表</param>
|
||
/// <returns>最近线段的前后两个点,如果找不到则返回null</returns>
|
||
private (PathPoint prevPoint, PathPoint nextPoint)? FindNearestLineSegment(Point3D previewPosition, List<PathPoint> pathPoints)
|
||
{
|
||
if (pathPoints == null || pathPoints.Count < 2)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
// 按索引排序路径点
|
||
var sortedPoints = pathPoints.OrderBy(p => p.Index).ToList();
|
||
|
||
double minDistance = double.MaxValue;
|
||
(PathPoint prevPoint, PathPoint nextPoint)? nearestSegment = null;
|
||
|
||
// 遍历相邻的路径点对,找到距离预览点最近的线段
|
||
for (int i = 0; i < sortedPoints.Count - 1; i++)
|
||
{
|
||
var currentPoint = sortedPoints[i];
|
||
var nextPoint = sortedPoints[i + 1];
|
||
|
||
// 计算预览点到线段的距离
|
||
var distance = CalculatePointToLineSegmentDistance(previewPosition, currentPoint.Position, nextPoint.Position);
|
||
|
||
if (distance < minDistance)
|
||
{
|
||
minDistance = distance;
|
||
nearestSegment = (currentPoint, nextPoint);
|
||
}
|
||
}
|
||
|
||
return nearestSegment;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算点到线段的距离
|
||
/// </summary>
|
||
/// <param name="point">目标点</param>
|
||
/// <param name="lineStart">线段起点</param>
|
||
/// <param name="lineEnd">线段终点</param>
|
||
/// <returns>点到线段的最短距离</returns>
|
||
private double CalculatePointToLineSegmentDistance(Point3D point, Point3D lineStart, Point3D lineEnd)
|
||
{
|
||
// 线段向量
|
||
var lineVector = new Point3D(lineEnd.X - lineStart.X, lineEnd.Y - lineStart.Y, lineEnd.Z - lineStart.Z);
|
||
|
||
// 点到线段起点的向量
|
||
var pointVector = new Point3D(point.X - lineStart.X, point.Y - lineStart.Y, point.Z - lineStart.Z);
|
||
|
||
// 计算线段长度的平方
|
||
var lineLengthSquared = lineVector.X * lineVector.X + lineVector.Y * lineVector.Y + lineVector.Z * lineVector.Z;
|
||
|
||
if (lineLengthSquared == 0)
|
||
{
|
||
// 线段退化为点,返回点到点的距离
|
||
return CalculateDistance(point, lineStart);
|
||
}
|
||
|
||
// 计算投影参数t
|
||
var t = (pointVector.X * lineVector.X + pointVector.Y * lineVector.Y + pointVector.Z * lineVector.Z) / lineLengthSquared;
|
||
|
||
// 将t限制在[0,1]范围内
|
||
t = Math.Max(0, Math.Min(1, t));
|
||
|
||
// 计算线段上最近点
|
||
var closestPoint = new Point3D(
|
||
lineStart.X + t * lineVector.X,
|
||
lineStart.Y + t * lineVector.Y,
|
||
lineStart.Z + t * lineVector.Z
|
||
);
|
||
|
||
// 返回点到最近点的距离
|
||
return CalculateDistance(point, closestPoint);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切点类型枚举
|
||
/// </summary>
|
||
public enum TangentPointType
|
||
{
|
||
/// <summary>
|
||
/// 进入切点(Ts)
|
||
/// </summary>
|
||
EntryTangent,
|
||
|
||
/// <summary>
|
||
/// 退出切点(Te)
|
||
/// </summary>
|
||
ExitTangent
|
||
}
|
||
|
||
/// <summary>
|
||
/// 方块标记数据结构(用于切点)
|
||
/// </summary>
|
||
public class SquareMarker
|
||
{
|
||
/// <summary>
|
||
/// 中心坐标
|
||
/// </summary>
|
||
public Point3D Center { get; set; }
|
||
|
||
/// <summary>
|
||
/// 方块面法向量
|
||
/// </summary>
|
||
public Vector3D Normal { get; set; }
|
||
|
||
/// <summary>
|
||
/// 方块边长
|
||
/// </summary>
|
||
public double Size { get; set; }
|
||
|
||
/// <summary>
|
||
/// 方块颜色
|
||
/// </summary>
|
||
public Color Color { get; set; }
|
||
|
||
/// <summary>
|
||
/// 不透明度 (1.0 = 完全不透明, 0.0 = 完全透明)
|
||
/// </summary>
|
||
public double Alpha { get; set; }
|
||
|
||
/// <summary>
|
||
/// 所属边ID
|
||
/// </summary>
|
||
public string EdgeId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 切点类型
|
||
/// </summary>
|
||
public TangentPointType TangentType { get; set; }
|
||
|
||
/// <summary>
|
||
/// 路径方向(用于渲染垂直薄片)
|
||
/// </summary>
|
||
public Vector3D Direction { get; set; }
|
||
|
||
/// <summary>
|
||
/// 高度(用于圆柱体渲染)
|
||
/// </summary>
|
||
public double Height { get; set; }
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"SquareMarker[类型={TangentType}, 中心=({Center.X:F2},{Center.Y:F2},{Center.Z:F2}), 边长={Size:F2}]";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 圆形标记数据结构
|
||
/// </summary>
|
||
public class CircleMarker
|
||
{
|
||
/// <summary>
|
||
/// 圆心坐标
|
||
/// </summary>
|
||
public Point3D Center { get; set; }
|
||
|
||
/// <summary>
|
||
/// 圆面法向量
|
||
/// </summary>
|
||
public Vector3D Normal { get; set; }
|
||
|
||
/// <summary>
|
||
/// 圆形半径
|
||
/// </summary>
|
||
public double Radius { get; set; }
|
||
|
||
/// <summary>
|
||
/// 圆形颜色
|
||
/// </summary>
|
||
public Color Color { get; set; }
|
||
|
||
/// <summary>
|
||
/// 不透明度 (1.0 = 完全不透明, 0.0 = 完全透明)
|
||
/// </summary>
|
||
public double Alpha { get; set; }
|
||
|
||
/// <summary>
|
||
/// 是否填充
|
||
/// </summary>
|
||
public bool Filled { get; set; }
|
||
|
||
/// <summary>
|
||
/// 路径点类型
|
||
/// </summary>
|
||
public PathPointType PointType { get; set; }
|
||
|
||
/// <summary>
|
||
/// 网格点类型
|
||
/// </summary>
|
||
public GridPointType GridPointType { get; set; } = GridPointType.Sphere;
|
||
|
||
/// <summary>
|
||
/// 序号
|
||
/// </summary>
|
||
public int SequenceNumber { get; set; }
|
||
|
||
/// <summary>
|
||
/// 创建时间
|
||
/// </summary>
|
||
public DateTime CreatedTime { get; set; }
|
||
/// <summary>
|
||
/// 是否可见
|
||
/// </summary>
|
||
public bool IsVisible { get; set; }
|
||
|
||
/// <summary>
|
||
/// 是否为不在路径上的控制点(半透明)
|
||
/// </summary>
|
||
public bool IsOffPath { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// 位置(Center的别名,用于预览点兼容性)
|
||
/// </summary>
|
||
public Point3D Position
|
||
{
|
||
get { return Center; }
|
||
set { Center = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关联的路径点对象
|
||
/// </summary>
|
||
public PathPoint PathPoint { get; set; }
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"CircleMarker[序号={SequenceNumber}, 类型={PointType}, 中心=({Center.X:F2},{Center.Y:F2},{Center.Z:F2}), 半径={Radius:F2}]";
|
||
}
|
||
}
|
||
|
||
public class PlaneMarker
|
||
{
|
||
public Point3D Origin { get; set; }
|
||
public Vector3D XVector { get; set; }
|
||
public Vector3D YVector { get; set; }
|
||
public Color Color { get; set; }
|
||
public double Alpha { get; set; }
|
||
public bool Filled { get; set; } = true;
|
||
}
|
||
}
|