NavisworksTransport/src/Core/PathPointRenderPlugin.cs

3570 lines
139 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.440%不透明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;
}
}