完善多层吊装路径

This commit is contained in:
tian 2026-02-24 17:25:15 +08:00
parent edcbd0802b
commit 62548197b3
7 changed files with 462 additions and 458 deletions

View File

@ -1689,47 +1689,46 @@ namespace NavisworksTransport
{
LogManager.Info($"[吊装路径] 正在完成吊装路径,处理终点和下降点");
// 确保至少有起吊点、提升点和至少一个空中路径点
if (CurrentRoute.Points.Count >= 3 && _hoistingGroundIntermediatePoints.Count > 0)
// 确保至少有起吊点、提升点
if (CurrentRoute.Points.Count >= 2)
{
// 获取起吊点用于获取地面Z坐标
var startPoint = CurrentRoute.Points.First();
// 获取吊装高度内部使用模型单位如果未设置则默认3米
double liftHeightModelUnits = CurrentRoute.LiftHeight > 0 ? CurrentRoute.LiftHeight : UnitsConverter.ConvertFromMeters(3.0);
// 获取最后一个路径点用于获取当前的空中XY位置
var lastPoint = CurrentRoute.Points.Last();
LogManager.Info($"[吊装路径] 最后一个路径点: {lastPoint.Name}, 位置: ({lastPoint.Position.X:F2}, {lastPoint.Position.Y:F2}, {lastPoint.Position.Z:F2})");
// 获取最后一个地面点击点(原始坐标,未被调整过)
var lastGroundClick = _hoistingGroundIntermediatePoints.Last();
LogManager.Debug($"[吊装路径] 最后一个地面点击点: ({lastGroundClick.X:F2}, {lastGroundClick.Y:F2}, {lastGroundClick.Z:F2})");
// 计算下一个索引
int nextIndex = CurrentRoute.Points.Count;
// 删除最后一个空中路径点(它的坐标可能被调整过,不使用任何信息)
CurrentRoute.Points.RemoveAt(CurrentRoute.Points.Count - 1);
// 创建落地点终点使用最后一个地面点击点的完整原始坐标X、Y、Z
var endPoint = new PathPoint(
new Point3D(lastGroundClick.X, lastGroundClick.Y, lastGroundClick.Z),
"落地点",
PathPointType.EndPoint);
endPoint.Direction = HoistingPointDirection.Vertical;
LogManager.Debug($"[吊装路径] 已创建落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})");
// 向上生成下降点在落地点正上方Z坐标为吊装高度
// 创建下降点在落地点正上方Z坐标为最后一个路径点的Z保持当前高度
var descendPoint = new PathPoint(
new Point3D(endPoint.Position.X, endPoint.Position.Y, startPoint.Position.Z + liftHeightModelUnits),
new Point3D(lastPoint.Position.X, lastPoint.Position.Y, lastPoint.Position.Z),
"下降点",
PathPointType.WayPoint);
descendPoint.Direction = HoistingPointDirection.Vertical;
LogManager.Debug($"[吊装路径] 已生成下降点(在终点正上方): ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})");
descendPoint.Index = nextIndex++;
LogManager.Info($"[吊装路径] 已生成下降点(在终点正上方): ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})");
// 创建落地点终点使用最后一个路径点的XY使用起点Z作为地面高度
var endPoint = new PathPoint(
new Point3D(lastPoint.Position.X, lastPoint.Position.Y, startPoint.Position.Z),
"落地点",
PathPointType.EndPoint);
endPoint.Direction = HoistingPointDirection.Vertical;
endPoint.Index = nextIndex++;
LogManager.Info($"[吊装路径] 已创建落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})");
// 添加下降点
CurrentRoute.Points.Add(descendPoint);
// 添加落地点(终点)
CurrentRoute.Points.Add(endPoint);
LogManager.Debug($"[吊装路径] 已添加落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})");
LogManager.Info($"[吊装路径] 已添加下降点和落地点,当前共 {CurrentRoute.Points.Count} 个路径点");
// 优化路径点:处理斜线和清除多余点
OptimizeRightAnglePathPoints(CurrentRoute);
// 注意:不需要再次调用 OrthogonalizePath因为添加的只是垂直线段
// 垂直线段不会形成斜线,所以不需要优化
// 注意TotalLength 现在是计算属性,自动从几何数据计算,无需手动更新
@ -1921,6 +1920,7 @@ namespace NavisworksTransport
/// <summary>
/// 检测两点之间是否为斜线,如果是则插入中间转折点或修正坐标
/// 处理三种斜线XY平面斜线、XZ平面斜线、YZ平面斜线
/// </summary>
/// <param name="points">路径点列表</param>
/// <param name="startIndex">开始检查的索引</param>
@ -1935,134 +1935,193 @@ namespace NavisworksTransport
var currentPoint = points[startIndex];
var nextPoint = points[startIndex + 1];
LogManager.Debug($"[斜线处理] 检查索引 {startIndex} 和 {startIndex + 1} 之间的连线");
LogManager.Debug($"[斜线处理] 当前点 [{startIndex}]: {currentPoint.Name}, 位置: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
LogManager.Debug($"[斜线处理] 下一点 [{startIndex + 1}]: {nextPoint.Name}, 位置: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})");
// 使用 Info 级别日志以便调试
LogManager.Info($"[斜线处理] 检查索引 {startIndex} 和 {startIndex + 1} 之间的连线");
LogManager.Info($"[斜线处理] 当前点 [{startIndex}]: {currentPoint.Name}, 位置: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
LogManager.Info($"[斜线处理] 下一点 [{startIndex + 1}]: {nextPoint.Name}, 位置: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})");
double deltaX = Math.Abs(nextPoint.Position.X - currentPoint.Position.X);
double deltaY = Math.Abs(nextPoint.Position.Y - currentPoint.Position.Y);
double deltaZ = Math.Abs(nextPoint.Position.Z - currentPoint.Position.Z);
// 检测斜线X和Y都变化
bool isDiagonal = deltaX > 0.01 && deltaY > 0.01;
LogManager.Info($"[斜线处理] deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2}");
if (!isDiagonal)
// 检测三种斜线
bool isXYDiagonal = deltaX > 0.01 && deltaY > 0.01 && deltaZ < 0.001; // XY平面斜线
bool isXZDiagonal = deltaX > 0.01 && deltaZ > 0.001 && deltaY < 0.01; // XZ平面斜线
bool isYZDiagonal = deltaY > 0.01 && deltaZ > 0.001 && deltaX < 0.01; // YZ平面斜线
bool isXYZDiagonal = deltaX > 0.01 && deltaY > 0.01 && deltaZ > 0.001; // XYZ空间斜线
LogManager.Info($"[斜线处理] 斜线检测结果: XY={isXYDiagonal}, XZ={isXZDiagonal}, YZ={isYZDiagonal}, XYZ={isXYZDiagonal}");
if (!isXYDiagonal && !isXZDiagonal && !isYZDiagonal && !isXYZDiagonal)
{
LogManager.Info($"[斜线处理] 未检测到斜线,跳过处理");
return false;
}
// 区分两种情况:
// 1. 真正的斜线deltaX和deltaY都比较大>2.0)→ 插入中间点
// 2. 微小偏差:其中一个值很小(<0.5)→ 修改下一点坐标,使其与上一点对齐
if (deltaX > 2.0 && deltaY > 2.0)
// 处理XY平面斜线水平斜线
if (isXYDiagonal)
{
// 真正的斜线:插入中间点
LogManager.Debug($"[斜线处理] 检测到斜线,需要插入中间点 (deltaX={deltaX:F2}, deltaY={deltaY:F2})");
int intermediateIndex = points.Count > 0 ? points.Count : 1;
var intermediatePoint = new PathPoint(
new Point3D(
nextPoint.Position.X, // 使用新点的X
currentPoint.Position.Y, // 保持上一个点的Y
currentPoint.Position.Z // 保持吊装高度
),
$"路径点{intermediateIndex}",
PathPointType.WayPoint);
intermediatePoint.Direction = HoistingPointDirection.Longitudinal; // 纵向移动
int insertPosition = startIndex + 1;
points.Insert(insertPosition, intermediatePoint);
// 更新所有点的索引
for (int i = 0; i < points.Count; i++)
{
points[i].Index = i;
}
LogManager.Debug($"[斜线处理] 已插入中间点: {intermediatePoint.Name}, 位置: ({intermediatePoint.Position.X:F2}, {intermediatePoint.Position.Y:F2}, {intermediatePoint.Position.Z:F2}) - 方向: 纵向");
LogManager.Debug($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}");
return true;
return HandleXYDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaY);
}
else if (deltaX < 0.5 && deltaY > 0.5)
// 处理XZ平面斜线垂直斜线X和Z同时变化
if (isXZDiagonal)
{
// X方向微小偏差Y方向移动修改下一点坐标保持X与上一点对齐垂直线
LogManager.Debug($"[斜线处理] 检测到X方向微小偏差 (deltaX={deltaX:F2}, deltaY={deltaY:F2}),修正下一点坐标使其垂直");
nextPoint.Position = new Point3D(
currentPoint.Position.X, // 保持上一个点的X
nextPoint.Position.Y, // 保持新点的Y
nextPoint.Position.Z); // 保持新点的Z
LogManager.Debug($"[斜线处理] 已修正下一点 {nextPoint.Name} 坐标: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})");
return true;
return HandleXZDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaZ);
}
else if (deltaY < 0.5 && deltaX > 0.5)
// 处理YZ平面斜线垂直斜线Y和Z同时变化
if (isYZDiagonal)
{
// Y方向微小偏差X方向移动修改下一点坐标保持Y与上一点对齐水平线
LogManager.Debug($"[斜线处理] 检测到Y方向微小偏差 (deltaX={deltaX:F2}, deltaY={deltaY:F2}),修正下一点坐标使其水平");
nextPoint.Position = new Point3D(
nextPoint.Position.X, // 保持新点的X
currentPoint.Position.Y, // 保持上一个点的Y
nextPoint.Position.Z); // 保持新点的Z
LogManager.Debug($"[斜线处理] 已修正下一点 {nextPoint.Name} 坐标: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})");
return true;
return HandleYZDiagonal(points, startIndex, currentPoint, nextPoint, deltaY, deltaZ);
}
// 处理XYZ空间斜线X、Y、Z都变化
if (isXYZDiagonal)
{
return HandleXYZDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaY, deltaZ);
}
return false;
}
/// <summary>
/// 处理XY平面斜线水平斜线
/// 策略:插入中间点将斜线拆分为直角转折,保持用户点击的坐标不变
/// </summary>
private bool HandleXYDiagonal(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaY)
{
LogManager.Debug($"[斜线处理] 检测到XY平面斜线 (deltaX={deltaX:F2}, deltaY={deltaY:F2})");
// 策略:插入中间点,将斜线拆分为 X-only → Y-only 的两段直角路径
// 不修改用户点击的坐标,只插入转折点
// 转折点X用新点的XY用旧点的Y先X方向移动再Y方向移动
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z),
HoistingPointDirection.Longitudinal);
}
/// <summary>
/// 处理XZ平面斜线垂直斜线X和Z同时变化
/// 策略先水平移动X变化Z不变再垂直移动Z变化X不变
/// </summary>
private bool HandleXZDiagonal(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaZ)
{
LogManager.Debug($"[斜线处理] 检测到XZ平面斜线 (deltaX={deltaX:F2}, deltaZ={deltaZ:F2})");
// 插入中间点先水平移动到新X旧Z目标点自然就是新X新Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z),
HoistingPointDirection.Longitudinal);
}
/// <summary>
/// 处理YZ平面斜线垂直斜线Y和Z同时变化
/// 策略先水平移动Y变化Z不变再垂直移动Z变化Y不变
/// </summary>
private bool HandleYZDiagonal(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaY, double deltaZ)
{
LogManager.Debug($"[斜线处理] 检测到YZ平面斜线 (deltaY={deltaY:F2}, deltaZ={deltaZ:F2})");
// 插入中间点先水平移动到新Y旧Z目标点自然就是新Y新Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(currentPoint.Position.X, nextPoint.Position.Y, currentPoint.Position.Z),
HoistingPointDirection.Lateral);
}
/// <summary>
/// 处理XYZ空间斜线X、Y、Z都变化
/// 策略先水平移动XY平面Z不变再垂直移动Z变化XY不变
/// 水平移动时如果XY也是斜线则进一步拆分
/// </summary>
private bool HandleXYZDiagonal(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaY, double deltaZ)
{
LogManager.Debug($"[斜线处理] 检测到XYZ空间斜线 (deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2})");
// 分两步:
// 1. 先水平移动到目标XY保持当前Z
// 2. 再垂直移动到目标Z保持XY不变
// 如果水平移动也是斜线X和Y都变化则先处理XY斜线
if (deltaX > 0.01 && deltaY > 0.01)
{
// 插入中间点先X方向移动保持Y到目标X后再处理Y和Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z),
HoistingPointDirection.Longitudinal);
}
else
{
// 中等程度的偏差,插入中间点
LogManager.Debug($"[斜线处理] 检测到中等程度斜线,需要插入中间点 (deltaX={deltaX:F2}, deltaY={deltaY:F2})");
int intermediateIndex = points.Count > 0 ? points.Count : 1;
var intermediatePoint = new PathPoint(
new Point3D(
nextPoint.Position.X, // 使用新点的X
currentPoint.Position.Y, // 保持上一个点的Y
currentPoint.Position.Z // 保持吊装高度
),
$"路径点{intermediateIndex}",
PathPointType.WayPoint);
intermediatePoint.Direction = HoistingPointDirection.Longitudinal; // 纵向移动
int insertPosition = startIndex + 1;
points.Insert(insertPosition, intermediatePoint);
// 更新所有点的索引
for (int i = 0; i < points.Count; i++)
// 水平方向只有一个轴变化,直接插入中间点处理垂直斜线
// 选择变化较大的水平轴作为先移动的方向
if (deltaX > deltaY)
{
points[i].Index = i;
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z),
HoistingPointDirection.Longitudinal);
}
else
{
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(currentPoint.Position.X, nextPoint.Position.Y, currentPoint.Position.Z),
HoistingPointDirection.Lateral);
}
LogManager.Debug($"[斜线处理] 已插入中间点: {intermediatePoint.Name}, 位置: ({intermediatePoint.Position.X:F2}, {intermediatePoint.Position.Y:F2}, {intermediatePoint.Position.Z:F2}) - 方向: 纵向");
LogManager.Debug($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}");
return true;
}
}
/// <summary>
/// 优化直角转折路径点:出现斜线时插入点,在同一直线上时清除多余点
/// 插入中间点的辅助方法
/// </summary>
/// <param name="route">要优化的路径</param>
/// <returns>是否进行了优化</returns>
public bool OptimizeRightAnglePathPoints(PathRoute route)
private bool InsertIntermediatePoint(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint,
Point3D intermediatePosition, HoistingPointDirection direction)
{
int intermediateIndex = points.Count > 0 ? points.Count : 1;
var intermediatePoint = new PathPoint(intermediatePosition, $"路径点{intermediateIndex}", PathPointType.WayPoint);
intermediatePoint.Direction = direction;
int insertPosition = startIndex + 1;
points.Insert(insertPosition, intermediatePoint);
// 更新所有点的索引
for (int i = 0; i < points.Count; i++)
{
points[i].Index = i;
}
LogManager.Info($"[斜线处理] 已插入中间点: {intermediatePoint.Name}, 位置: ({intermediatePoint.Position.X:F2}, {intermediatePoint.Position.Y:F2}, {intermediatePoint.Position.Z:F2}) - 方向: {direction}");
LogManager.Info($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}");
return true;
}
/// <summary>
/// 正交化路径:将斜线路径转换为轴对齐的直角路径
/// 出现斜线时插入转折点使所有线段平行于X、Y或Z轴
/// </summary>
/// <param name="route">要正交化的路径</param>
/// <param name="startIndex">开始正交化的索引默认从0开始处理所有点。用于增量处理避免修改已处理的点。</param>
/// <returns>是否进行了正交化</returns>
public bool OrthogonalizePath(PathRoute route, int startIndex = 0)
{
if (route == null || route.Points.Count < 2)
{
return false;
}
// 确保startIndex有效
if (startIndex < 0) startIndex = 0;
if (startIndex >= route.Points.Count - 1) return false;
bool hasChanges = false;
var points = route.Points;
double tolerance = 0.001; // 容差,用于判断是否在同一直线上
double smallDeviation = 0.5; // 微小偏差阈值,用于修正坐标
double minDistance = 1.0; // 最小距离阈值,小于此距离不删除点
// 第一步:处理斜线,插入转折点或修正坐标
// 注意由于可能插入点需要从后往前遍历或使用while循环
int i = 0;
// 从startIndex开始只处理新添加的点
int i = startIndex;
while (i < points.Count - 1)
{
int originalCount = points.Count;
@ -2074,7 +2133,7 @@ namespace NavisworksTransport
if (points.Count > originalCount)
{
// 插入了点,继续检查当前索引(因为插入点可能需要进一步优化)
LogManager.Debug($"[直角路径优化] 索引{i}处插入了点,继续检查");
LogManager.Info($"[直角路径优化] 索引{i}处插入了点,继续检查");
}
else
{
@ -2088,64 +2147,10 @@ namespace NavisworksTransport
}
}
// 第二步处理微小转折X或Y方向微小偏差
for (i = 1; i < points.Count - 1; i++)
{
var prevPoint = points[i - 1];
var currentPoint = points[i];
var nextPoint = points[i + 1];
// 计算两个方向的偏差
double deltaX1 = Math.Abs(currentPoint.Position.X - prevPoint.Position.X);
double deltaY1 = Math.Abs(currentPoint.Position.Y - prevPoint.Position.Y);
double deltaX2 = Math.Abs(nextPoint.Position.X - currentPoint.Position.X);
double deltaY2 = Math.Abs(nextPoint.Position.Y - currentPoint.Position.Y);
// 检测微小转折:前一段和后一段都有微小偏差,但方向不同
// 例如前一段X变化很小Y变化较大后一段X变化较大Y变化很小
bool hasSmallTurn = false;
// 前一段垂直,后一段水平
if (deltaX1 < smallDeviation && deltaY1 > smallDeviation &&
deltaY2 < smallDeviation && deltaX2 > smallDeviation)
{
hasSmallTurn = true;
}
// 前一段水平,后一段垂直
else if (deltaY1 < smallDeviation && deltaX1 > smallDeviation &&
deltaX2 < smallDeviation && deltaY2 > smallDeviation)
{
hasSmallTurn = true;
}
if (hasSmallTurn && currentPoint.Type == PathPointType.WayPoint)
{
// 修正转折点坐标,使其完全垂直
if (deltaX1 < smallDeviation)
{
// 前一段垂直调整转折点X与上一点对齐
currentPoint.Position = new Point3D(
prevPoint.Position.X,
currentPoint.Position.Y,
currentPoint.Position.Z);
LogManager.Debug($"[直角路径优化] 修正微小转折点 {i} X坐标: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
hasChanges = true;
}
else
{
// 前一段水平调整转折点Y与上一点对齐
currentPoint.Position = new Point3D(
currentPoint.Position.X,
prevPoint.Position.Y,
currentPoint.Position.Z);
LogManager.Debug($"[直角路径优化] 修正微小转折点 {i} Y坐标: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
hasChanges = true;
}
}
}
// 第三步:清除在同一直线上的多余点
i = 1;
// 第二步:清除在同一直线上的多余点
// 只检查从startIndex开始的新插入点避免修改已处理的路径
int startForCollinear = Math.Max(1, startIndex);
i = startForCollinear;
while (i < points.Count - 1)
{
var prevPoint = points[i - 1];
@ -2166,7 +2171,20 @@ namespace NavisworksTransport
continue;
}
// 判断三个点是否在同一直线上只检查X或Y方向相同
// 判断三个点是否在同一直线上需要在3D空间中共线
// 首先检查Z坐标是否相同在同一平面上
double deltaZ1 = Math.Abs(currentPoint.Position.Z - prevPoint.Position.Z);
double deltaZ2 = Math.Abs(nextPoint.Position.Z - currentPoint.Position.Z);
bool sameZ = deltaZ1 < tolerance && deltaZ2 < tolerance;
// 如果Z不同这三个点一定不共线一个是垂直过渡点
if (!sameZ)
{
i++;
continue;
}
// 在同一平面上检查XY方向是否共线
bool sameX = Math.Abs(prevPoint.Position.X - currentPoint.Position.X) < tolerance &&
Math.Abs(currentPoint.Position.X - nextPoint.Position.X) < tolerance;
bool sameY = Math.Abs(prevPoint.Position.Y - currentPoint.Position.Y) < tolerance &&
@ -2185,7 +2203,7 @@ namespace NavisworksTransport
// 删除中间点
points.RemoveAt(i);
hasChanges = true;
LogManager.Debug($"[直角路径优化] 删除共线的多余点 {i}: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
LogManager.Info($"[直角路径优化] 删除共线的多余点 {i}: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
// 不调整索引,因为删除了一个点,新位置的点会在下一次循环被检查
continue;
@ -2376,8 +2394,8 @@ namespace NavisworksTransport
UpdateAerialPathRelatedPoints(route, pointIndex);
}
// 吊装路径:优化路径点(处理斜线和清除多余点)
OptimizeRightAnglePathPoints(route);
// 吊装路径:正交化路径(处理斜线和清除多余点)
OrthogonalizePath(route);
}
else
{
@ -2876,35 +2894,40 @@ namespace NavisworksTransport
return null;
}
// 获取吊装高度LiftHeight内部是模型单位,转换为米传给方法
double liftHeightMeters = CurrentRoute.LiftHeight > 0
? UnitsConverter.ConvertToMeters(CurrentRoute.LiftHeight)
: 3.0;
// 获取吊装高度LiftHeight内部是模型单位
double liftHeightModelUnits = CurrentRoute.LiftHeight > 0
? CurrentRoute.LiftHeight
: UnitsConverter.ConvertFromMeters(3.0);
// 使用 AerialPathGenerator 生成空中路径点
var generator = new AerialPathGenerator();
confirmPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
_previewPoint.Position, // 用户点击的地面位置
liftHeightMeters,
liftHeightModelUnits, // 直接使用模型单位
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
LogManager.Info($"[预览点-吊装路径] 已生成空中路径点: {confirmPoint.Name}, 位置: ({confirmPoint.Position.X:F2}, {confirmPoint.Position.Y:F2}, {confirmPoint.Position.Z:F2})");
// 插入到正确位置
int optimizeStartIndex;
if (_previewInsertIndex >= 0 && _previewInsertIndex <= CurrentRoute.Points.Count)
{
CurrentRoute.InsertPoint(confirmPoint, _previewInsertIndex);
LogManager.Info($"[预览点-吊装路径] 已插入到索引 {_previewInsertIndex}");
// 从插入位置的前一个点开始优化
optimizeStartIndex = Math.Max(0, _previewInsertIndex - 1);
}
else
{
// 追加模式:记录添加前的点数
optimizeStartIndex = Math.Max(0, CurrentRoute.Points.Count - 1);
CurrentRoute.AddPoint(confirmPoint);
LogManager.Info($"[预览点-吊装路径] 已追加到末尾");
}
// 调用路径优化方法处理斜线、微小转折和共线点
OptimizeRightAnglePathPoints(CurrentRoute);
// 调用正交化方法处理斜线、微小转折和共线点(增量处理)
OrthogonalizePath(CurrentRoute, optimizeStartIndex);
LogManager.Info($"[预览点-吊装路径] 路径优化完成,当前路径点数: {CurrentRoute.Points.Count}");
}
else
@ -3611,70 +3634,132 @@ namespace NavisworksTransport
}
else
{
// 后续点击:添加空中路径点
LogManager.Debug($"[吊装模式] 用户点击: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})");
// 后续点击:添加新的层级(每次都需要输入高度)
LogManager.Debug($"[吊装模式] 用户点击,准备添加新层级: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})");
// 获取吊装高度(模型单位)
double liftHeightModelUnits = _hoistingLiftHeight;
if (liftHeightModelUnits <= 0)
// 记录地面点击位置后续在UI线程中处理
Point3D groundPoint = clickedPoint;
// 在UI线程中弹出高度对话框并生成路径点
_uiStateManager.QueueUIUpdate(() =>
{
liftHeightModelUnits = UnitsConverter.ConvertFromMeters(3.0);
LogManager.Warning($"[吊装模式] 吊装高度无效,使用默认值: 3.0米");
}
// 使用 AerialPathGenerator 生成单个空中路径点(智能方向判断)
try
{
var generator = new AerialPathGenerator();
// 获取上一个路径点(用于方向判断)
Point3D previousPoint;
if (CurrentRoute.Points.Count > 0)
try
{
// 如果已有路径点,使用最后一个路径点
previousPoint = CurrentRoute.Points.Last().Position;
}
else
{
// 如果还没有路径点(第一次添加中间点),使用提升点
previousPoint = new Point3D(
_hoistingStartPoint.X,
_hoistingStartPoint.Y,
_hoistingStartPoint.Z + liftHeightModelUnits);
}
// 计算默认高度上一次的高度如果没有则使用3米
double defaultHeightMeters = _hoistingLiftHeight > 0
? UnitsConverter.ConvertToMeters(_hoistingLiftHeight)
: 3.0;
var heightDialog = new NavisworksTransport.UI.WPF.Views.AerialHeightDialog(defaultHeight: defaultHeightMeters);
bool? dialogResult = heightDialog.ShowDialog();
// 生成空中路径点
var aerialPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
clickedPoint,
liftHeightModelUnits,
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
if (aerialPoint != null)
{
// 保存上一个路径点的索引(用于插入中间点)
int previousPointIndex = CurrentRoute.Points.Count - 1;
var previousPathPoint = CurrentRoute.Points[previousPointIndex];
LogManager.Debug($"[吊装模式] 上一个路径点索引: {previousPointIndex}, 名称: {previousPathPoint.Name}, 位置: ({previousPathPoint.Position.X:F2}, {previousPathPoint.Position.Y:F2})");
// 记录原始地面点击位置
_hoistingGroundIntermediatePoints.Add(clickedPoint);
LogManager.Debug($"[吊装模式] 已记录地面点击点: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})");
// 添加空中路径点(临时)
CurrentRoute.AddPoint(aerialPoint);
LogManager.Debug($"[吊装模式] 已添加空中路径点: {aerialPoint.Name}, 位置: ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}), 当前路径点数: {CurrentRoute.Points.Count}");
// 调用路径优化方法处理斜线、微小转折和共线点
OptimizeRightAnglePathPoints(CurrentRoute);
LogManager.Debug($"[吊装模式] 路径优化完成,当前路径点数: {CurrentRoute.Points.Count}");
for (int i = 0; i < CurrentRoute.Points.Count; i++)
// 如果用户取消,则只是跳过这次点击,继续等待下一次点击
if (dialogResult != true)
{
var p = CurrentRoute.Points[i];
LogManager.Debug($" [{i}] {p.Name}: ({p.Position.X:F2}, {p.Position.Y:F2}, {p.Position.Z:F2})");
LogManager.Info("[吊装模式] 用户取消了高度设置,继续等待点击");
RaiseStatusChanged("已取消,继续点击添加层级或点击完成按钮", PathPlanningStatusType.Info);
return;
}
LogManager.Debug($"[吊装模式] 已添加空中路径点: {aerialPoint.Name} ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}, {aerialPoint.Position.Z:F2}) - 方向: {aerialPoint.Direction}");
// 检查用户是否选择"设为终点"(完成路径)
bool isSetAsEndPoint = heightDialog.IsSetAsEndPoint;
// 获取用户设置的高度
double liftHeightMetersInput = heightDialog.LiftHeightMeters;
double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMetersInput);
double previousHeight = _hoistingLiftHeight;
_hoistingLiftHeight = liftHeightModelUnits;
// 更新路径的吊装高度(使用最新的高度)
if (CurrentRoute != null)
{
CurrentRoute.LiftHeight = liftHeightModelUnits;
}
LogManager.Info($"[吊装模式] 用户设置高度: {liftHeightMetersInput:F2}米,上一高度: {UnitsConverter.ConvertToMeters(previousHeight):F2}米");
// 获取上一个路径点
Point3D previousPoint;
if (CurrentRoute.Points.Count > 0)
{
previousPoint = CurrentRoute.Points.Last().Position;
var lastPoint = CurrentRoute.Points.Last();
LogManager.Info($"[吊装模式] 上一路径点: {lastPoint.Name}, Z={lastPoint.Position.Z:F2}");
}
else
{
previousPoint = new Point3D(
_hoistingStartPoint.X,
_hoistingStartPoint.Y,
_hoistingStartPoint.Z + liftHeightModelUnits);
LogManager.Info($"[吊装模式] 无上一路径点,使用起点+高度: Z={previousPoint.Z:F2}");
}
// 记录原始地面点击位置
_hoistingGroundIntermediatePoints.Add(groundPoint);
LogManager.Debug($"[吊装模式] 已记录地面点击点: ({groundPoint.X:F2}, {groundPoint.Y:F2}, {groundPoint.Z:F2})");
// 检查高度是否变化(比较绝对高度)
// liftHeightModelUnits 是相对地面的高度需要加上起点Z得到绝对高度
double newAbsoluteHeight = _hoistingStartPoint.Z + liftHeightModelUnits;
double heightDiff = Math.Abs(newAbsoluteHeight - previousPoint.Z);
bool heightChanged = heightDiff > 0.001;
LogManager.Info($"[吊装模式] 起点=({_hoistingStartPoint.X:F2}, {_hoistingStartPoint.Y:F2}, {_hoistingStartPoint.Z:F2})");
LogManager.Info($"[吊装模式] 相对高度(米)={liftHeightMetersInput:F2}, 相对高度(模型)={liftHeightModelUnits:F2}");
LogManager.Info($"[吊装模式] 新绝对高度={newAbsoluteHeight:F2}, 上一位置Z={previousPoint.Z:F2}");
// 步骤1水平移动点在旧高度上移动到新XY
// 使用 AerialPathGenerator 生成水平路径点
LogManager.Info($"[吊装模式] 生成水平路径点: previousPoint=({previousPoint.X:F2}, {previousPoint.Y:F2}, {previousPoint.Z:F2}), groundPoint=({groundPoint.X:F2}, {groundPoint.Y:F2}, {groundPoint.Z:F2})");
// 记录添加新点前的点数,用于增量优化
int pointCountBefore = CurrentRoute.Points.Count;
var generator = new AerialPathGenerator();
var aerialPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
groundPoint,
previousPoint.Z, // 保持旧高度
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
if (aerialPoint != null)
{
// 添加水平移动路径点
CurrentRoute.AddPoint(aerialPoint);
LogManager.Info($"[吊装模式] 已添加水平路径点: {aerialPoint.Name}, 位置: ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}, {aerialPoint.Position.Z:F2})");
// 更新上一个点为水平路径点
previousPoint = aerialPoint.Position;
}
// 步骤2在添加垂直过渡点前先正交化水平路径此时所有点在同一高度可以处理斜线
// 使用增量处理,只处理新添加的点(从添加前最后一个点的索引开始)
int orthogonalizeStartIndex = Math.Max(0, pointCountBefore - 1);
OrthogonalizePath(CurrentRoute, orthogonalizeStartIndex);
// 步骤3如果需要添加垂直过渡点
if (heightChanged)
{
// 高度变化:垂直上升到新高度
LogManager.Info($"[吊装模式] 高度变化,插入垂直过渡点: Z={previousPoint.Z:F2} -> Z={newAbsoluteHeight:F2}");
var verticalTransitionPoint = new Point3D(
previousPoint.X,
previousPoint.Y,
newAbsoluteHeight);
var verticalPoint = new PathPoint(
verticalTransitionPoint,
$"高度过渡{CurrentRoute.Points.Count}",
PathPointType.WayPoint);
verticalPoint.Direction = HoistingPointDirection.Vertical;
CurrentRoute.AddPoint(verticalPoint);
LogManager.Info($"[吊装模式] 已添加垂直过渡点: ({verticalTransitionPoint.X:F2}, {verticalTransitionPoint.Y:F2}, {verticalTransitionPoint.Z:F2})");
}
else
{
LogManager.Info($"[吊装模式] 高度未变化,无需垂直过渡");
}
// 渲染路径
if (_renderPlugin != null)
@ -3682,15 +3767,28 @@ namespace NavisworksTransport
_renderPlugin.RenderPath(CurrentRoute);
}
// 提示用户可以继续添加或点击完成
string directionText = aerialPoint.Direction == HoistingPointDirection.Longitudinal ? "纵向" : "横向";
RaiseStatusChanged($"已添加{directionText}路径点,继续点击或点击完成按钮", PathPlanningStatusType.Info);
// 如果用户选择"设为终点",调用 FinishEditing 完成路径
if (isSetAsEndPoint)
{
LogManager.Info("[吊装模式] 用户选择设为终点,调用 FinishEditing 完成路径");
bool finished = FinishEditing();
if (finished && CurrentRoute != null)
{
// 触发路径列表更新事件通知UI刷新
RaisePathPointsListUpdated(CurrentRoute, "设为终点完成路径");
}
}
else
{
string transitionText = heightChanged ? "(含高度过渡)" : "";
RaiseStatusChanged($"已添加层级路径点{transitionText}(高度: {liftHeightMetersInput:F2}米),继续点击添加新层级或点击完成按钮", PathPlanningStatusType.Info);
}
}
}
catch (Exception ex)
{
LogManager.Error($"[吊装模式] 生成空中路径点失败: {ex.Message}", ex);
}
catch (Exception ex)
{
LogManager.Error($"[吊装模式] 添加层级失败: {ex.Message}", ex);
}
}, UIUpdatePriority.High);
return;
}

View File

@ -1494,16 +1494,9 @@ namespace NavisworksTransport
}
else if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
{
if (isVerticalSegment)
{
// 吊装路径的垂直段(起吊段和下降段):通行空间底面在地面,不需要偏移
verticalOffset = 0;
}
else
{
// 吊装的水平段:路径点是悬挂点位置,通行空间顶面在悬挂点,中心需要向上偏移半个高度
verticalOffset = height / 2.0;
}
// 吊装路径:物体顶面中心就是路径点,通行空间整体向下移动半个高度
// 这样通行空间顶面中心对齐路径点
verticalOffset = -height / 2.0;
}
}
@ -1575,16 +1568,16 @@ namespace NavisworksTransport
if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001)
{
// 通行空间模式且有垂直偏移时,沿着up向量方向平移
// 通行空间模式且有垂直偏移时,沿Z轴平移确保只有垂直偏移没有水平偏移
adjustedStartPoint = new Point3D(
startPoint.X + up.X * verticalOffset,
startPoint.Y + up.Y * verticalOffset,
startPoint.Z + up.Z * verticalOffset
startPoint.X,
startPoint.Y,
startPoint.Z + verticalOffset
);
adjustedEndPoint = new Point3D(
endPoint.X + up.X * verticalOffset,
endPoint.Y + up.Y * verticalOffset,
endPoint.Z + up.Z * verticalOffset
endPoint.X,
endPoint.Y,
endPoint.Z + verticalOffset
);
}
else
@ -1646,28 +1639,28 @@ namespace NavisworksTransport
if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001)
{
// 通行空间模式且有垂直偏移时,沿着up向量方向平移
// 通行空间模式且有垂直偏移时,沿Z轴平移确保只有垂直偏移没有水平偏移
adjustedStartPoint = new Point3D(
startPoint.X + up.X * verticalOffset,
startPoint.Y + up.Y * verticalOffset,
startPoint.Z + up.Z * verticalOffset
startPoint.X,
startPoint.Y,
startPoint.Z + verticalOffset
);
adjustedEndPoint = new Point3D(
endPoint.X + up.X * verticalOffset,
endPoint.Y + up.Y * verticalOffset,
endPoint.Z + up.Z * verticalOffset
endPoint.X,
endPoint.Y,
endPoint.Z + verticalOffset
);
// 对 SampledPoints 应用垂直偏移(沿着up向量方向
// 对 SampledPoints 应用垂直偏移(沿Z轴
adjustedSampledPoints = new List<Point3D>();
if (edge.SampledPoints != null)
{
foreach (var point in edge.SampledPoints)
{
adjustedSampledPoints.Add(new Point3D(
point.X + up.X * verticalOffset,
point.Y + up.Y * verticalOffset,
point.Z + up.Z * verticalOffset
point.X,
point.Y,
point.Z + verticalOffset
));
}
}
@ -1873,17 +1866,37 @@ namespace NavisworksTransport
if (_showObjectSpace && Math.Abs(verticalOffset) > 0.001)
{
// 通行空间模式且有垂直偏移时沿着up向量方向平移
adjustedStartPoint = new Point3D(
startPoint.Position.X + up.X * verticalOffset,
startPoint.Position.Y + up.Y * verticalOffset,
startPoint.Position.Z + up.Z * verticalOffset
);
adjustedEndPoint = new Point3D(
endPoint.Position.X + up.X * verticalOffset,
endPoint.Position.Y + up.Y * verticalOffset,
endPoint.Position.Z + up.Z * verticalOffset
);
// 通行空间模式且有垂直偏移时
// 垂直空中路径只沿Z轴偏移没有水平偏移
// 水平路径沿up向量偏移up是垂直的
if (isVerticalSegment)
{
// 垂直段只沿Z轴偏移
adjustedStartPoint = new Point3D(
startPoint.Position.X,
startPoint.Position.Y,
startPoint.Position.Z + verticalOffset
);
adjustedEndPoint = new Point3D(
endPoint.Position.X,
endPoint.Position.Y,
endPoint.Position.Z + verticalOffset
);
}
else
{
// 水平段沿up向量偏移up是垂直的
adjustedStartPoint = new Point3D(
startPoint.Position.X + up.X * verticalOffset,
startPoint.Position.Y + up.Y * verticalOffset,
startPoint.Position.Z + up.Z * verticalOffset
);
adjustedEndPoint = new Point3D(
endPoint.Position.X + up.X * verticalOffset,
endPoint.Position.Y + up.Y * verticalOffset,
endPoint.Position.Z + up.Z * verticalOffset
);
}
}
else
{
@ -2789,6 +2802,7 @@ namespace NavisworksTransport
// 直线段:单个长方体
// 设置颜色和透明度
graphics.Color(lineMarker.Color, lineMarker.Opacity);
RenderCuboidMarker(
graphics,
lineMarker.StartPoint,

View File

@ -89,31 +89,30 @@ namespace NavisworksTransport.PathPlanning
/// </summary>
/// <param name="previousPoint">上一个路径点</param>
/// <param name="clickedGroundPoint">用户点击的地面投影位置</param>
/// <param name="liftHeightMeters">吊装高度(米</param>
/// <param name="targetHeightModelUnits">目标高度(模型单位</param>
/// <param name="pointIndex">路径点序号</param>
/// <returns>生成的路径点</returns>
public PathPoint GenerateSmartHoistingPoint(
Point3D previousPoint,
Point3D clickedGroundPoint,
double liftHeightMeters,
double targetHeightModelUnits,
int pointIndex)
{
if (previousPoint == null)
throw new ArgumentNullException(nameof(previousPoint));
if (clickedGroundPoint == null)
throw new ArgumentNullException(nameof(clickedGroundPoint));
if (liftHeightMeters <= 0)
throw new ArgumentException("提升高度必须大于0", nameof(liftHeightMeters));
// 注意targetHeightModelUnits 是绝对高度,可以是负数(如果起点在地下)
// 计算两个方向的距离
double deltaX = Math.Abs(clickedGroundPoint.X - previousPoint.X);
double deltaY = Math.Abs(clickedGroundPoint.Y - previousPoint.Y);
// 直接使用地面点的完整XY值不调整位置
// 使用目标高度作为Z坐标XY使用地面点击位置
Point3D newPoint = new Point3D(
clickedGroundPoint.X,
clickedGroundPoint.Y,
previousPoint.Z); // 使用上一个路径点的Z坐标保持吊装高度
targetHeightModelUnits); // 使用传入的目标高度
// 判断移动方向(仅用于记录,不影响位置)
HoistingPointDirection direction;
@ -195,7 +194,7 @@ namespace NavisworksTransport.PathPlanning
var newPoint = GenerateSmartHoistingPoint(
previousPoint,
groundIntermediatePoints[i],
liftHeightMeters,
liftHeightModelUnits, // 使用模型单位的高度
i + 1);
pathPoints.Add(newPoint);

View File

@ -2685,10 +2685,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
coreRoute.Points.Remove(corePoint);
LogManager.Info($"已从Core数据删除路径点: {corePoint.Name}");
// 吊装路径:优化路径点(处理斜线和清除多余点)
// 吊装路径:正交化路径(处理斜线和清除多余点)
if (coreRoute.PathType == PathType.Hoisting)
{
_pathPlanningManager?.OptimizeRightAnglePathPoints(coreRoute);
_pathPlanningManager?.OrthogonalizePath(coreRoute);
}
// 调用PathPlanningManager的3D删除方法

View File

@ -55,17 +55,28 @@
<!-- 按钮栏 -->
<Border Grid.Row="2" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,1,0,0" Padding="20,12">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="确定"
<Button Content="继续"
Click="OnOKClick"
Style="{StaticResource ActionButtonStyle}"
Width="90"
Height="32"
Margin="0,0,10,0"/>
Margin="0,0,10,0"
ToolTip="设置高度并继续添加更多路径点"/>
<Button Content="设为终点"
Click="OnSetAsEndPointClick"
Style="{StaticResource SecondaryButtonStyle}"
Width="90"
Height="32"
Margin="0,0,10,0"
Background="#FFE8F5E9"
Foreground="#FF2E7D32"
ToolTip="将此点设为路径终点,直接降落完成路径"/>
<Button Content="取消"
Click="OnCancelClick"
Style="{StaticResource SecondaryButtonStyle}"
Width="90"
Height="32"/>
Height="32"
ToolTip="取消这次点击"/>
</StackPanel>
</Border>
</Grid>

View File

@ -13,6 +13,11 @@ namespace NavisworksTransport.UI.WPF.Views
/// </summary>
public double LiftHeightMeters { get; private set; }
/// <summary>
/// 获取是否设为终点(完成路径)
/// </summary>
public bool IsSetAsEndPoint { get; private set; }
/// <summary>
/// 构造函数
/// </summary>
@ -31,19 +36,26 @@ namespace NavisworksTransport.UI.WPF.Views
}
/// <summary>
/// 确定按钮点击事件
/// 确定按钮点击事件 - 添加层级(继续)
/// </summary>
private void OnOKClick(object sender, RoutedEventArgs e)
{
if (double.TryParse(HeightTextBox.Text, out double height) && height > 0)
if (ValidateHeight())
{
LiftHeightMeters = height;
IsSetAsEndPoint = false;
DialogResult = true;
}
else
}
/// <summary>
/// 设为终点按钮点击事件 - 完成路径
/// </summary>
private void OnSetAsEndPointClick(object sender, RoutedEventArgs e)
{
if (ValidateHeight())
{
System.Windows.MessageBox.Show("请输入有效的提升高度大于0的数字", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
IsSetAsEndPoint = true;
DialogResult = true;
}
}
@ -54,5 +66,23 @@ namespace NavisworksTransport.UI.WPF.Views
{
DialogResult = false;
}
/// <summary>
/// 验证输入的高度是否有效
/// </summary>
private bool ValidateHeight()
{
if (double.TryParse(HeightTextBox.Text, out double height) && height > 0)
{
LiftHeightMeters = height;
return true;
}
else
{
System.Windows.MessageBox.Show("请输入有效的提升高度大于0的数字", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
}
}
}

View File

@ -213,11 +213,7 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
<Button Content="吊装路径"
Command="{Binding NewHoistingPathCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="创建吊装路径"/>
<Button Content="多层吊装"
Command="{Binding NewMultiLevelHoistingPathCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="创建多层阶梯式吊装路径"/>
ToolTip="创建吊装路径(支持多层)"/>
<Button Content="删除"
Command="{Binding DeletePathCommand}"
Style="{StaticResource SecondaryButtonStyle}"
@ -270,151 +266,7 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
</StackPanel>
</Border>
<!-- 区域3: 多层吊装路径 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10"
Visibility="{Binding IsMultiLevelHoistingMode, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel>
<Label Content="多层阶梯式吊装路径" Style="{StaticResource SectionHeaderStyle}"/>
<TextBlock Text="设置多个高度层级,支持上升和下降的阶梯式移动"
FontSize="10" Foreground="#FF666666" Margin="0,-5,0,5"/>
<!-- 起点/终点设置 -->
<Grid Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 起点 -->
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Button Content="选择起点"
Command="{Binding SelectMultiLevelStartPointCommand}"
Style="{StaticResource SecondaryButtonStyle}"
ToolTip="在3D视图中点击选择地面起吊位置"/>
<TextBox Text="{Binding MultiLevelStartPointText, Mode=OneWay}"
Style="{StaticResource ReadOnlyTextBoxStyle}"
Width="100"/>
</StackPanel>
<!-- 终点 -->
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Content="选择终点"
Command="{Binding SelectMultiLevelEndPointCommand}"
Style="{StaticResource SecondaryButtonStyle}"
ToolTip="在3D视图中点击选择最终放置位置"/>
<TextBox Text="{Binding MultiLevelEndPointText, Mode=OneWay}"
Style="{StaticResource ReadOnlyTextBoxStyle}"
Width="100"/>
</StackPanel>
</Grid>
<!-- 层级列表标题 -->
<Grid Margin="0,10,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="层级列表" VerticalAlignment="Center" FontSize="11" Foreground="#FF666666"/>
<Button Grid.Column="1" Content="+ 添加层级"
Command="{Binding AddMultiLevelCommand}"
Style="{StaticResource SecondaryButtonStyle}"
Width="80" Height="24"/>
</Grid>
<!-- 层级列表 -->
<DataGrid ItemsSource="{Binding MultiLevelItems}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Single"
GridLinesVisibility="Horizontal"
AlternatingRowBackground="#FFF0F8FF"
HeadersVisibility="Column"
RowHeaderWidth="0"
Height="150">
<DataGrid.Columns>
<!-- 序号 -->
<DataGridTextColumn Header="序号" Binding="{Binding Index, Converter={StaticResource IndexPlusOneConverter}}" Width="45" IsReadOnly="True"/>
<!-- 高度 -->
<DataGridTemplateColumn Header="高度(m)" Width="80">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding HeightInMeters, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Width="70" Height="22" FontSize="10"
VerticalAlignment="Center" VerticalContentAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- 方向 -->
<DataGridTemplateColumn Header="方向" Width="55">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton Content="{Binding IsRise, Converter={StaticResource DirectionToTextConverter}}"
IsChecked="{Binding IsRise, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="50" Height="22" FontSize="10"
Background="{Binding IsRise, Converter={StaticResource DirectionToColorConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- 目标位置 -->
<DataGridTemplateColumn Header="目标位置" Width="*" MinWidth="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding HorizontalTarget, Converter={StaticResource TargetToTextConverter}}"
VerticalAlignment="Center" Width="70" FontSize="10"/>
<Button Content="📍"
Command="{Binding DataContext.PickMultiLevelTargetCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
Width="24" Height="22" FontSize="10"
ToolTip="拾取目标位置"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- 操作 -->
<DataGridTemplateColumn Header="操作" Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="×"
Command="{Binding DataContext.DeleteMultiLevelCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
Width="24" Height="22"
Foreground="Red" FontWeight="Bold"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- 统计信息和创建按钮 -->
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding MultiLevelStatsText}"
VerticalAlignment="Center" FontSize="10" Foreground="#FF666666"/>
<Button Grid.Column="1" Content="创建路径"
Command="{Binding CreateMultiLevelPathCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanCreateMultiLevelPath}"/>
</Grid>
<!-- 取消按钮 -->
<Button Content="取消多层吊装"
Command="{Binding CancelMultiLevelHoistingCommand}"
Style="{StaticResource SecondaryButtonStyle}"
HorizontalAlignment="Left"
Margin="0,10,0,0"/>
</StackPanel>
</Border>
<!-- 区域4: 路径编辑 -->
<!-- 区域3: 路径编辑 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10">
<StackPanel>
<Label Content="路径编辑" Style="{StaticResource SectionHeaderStyle}"/>