From 810f874a50dce8e0a26d3246d19b2489e9e8d2ef Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Mon, 15 Sep 2025 00:29:12 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BA=86=E7=BD=91=E6=A0=BC?=
=?UTF-8?q?=E5=9C=B0=E5=9B=BE=E7=9A=84z=E5=9D=90=E6=A0=87=E8=AE=BE?=
=?UTF-8?q?=E7=BD=AE=EF=BC=8C=E6=94=B9=E8=BF=9B=E4=BA=86=E8=B7=AF=E5=BE=84?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=E6=AD=A5=E9=AA=A4=EF=BC=8C=E8=B7=AF=E5=BE=84?=
=?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=9C=A8=E5=8F=B0=E9=98=B6=E5=A4=84=E4=BF=9D?=
=?UTF-8?q?=E7=95=992=E4=B8=AA=E8=B7=AF=E5=BE=84=E7=82=B9=EF=BC=8C?=
=?UTF-8?q?=E6=9B=B4=E5=A5=BD=E8=B4=B4=E5=90=88=E9=80=9A=E9=81=93=E8=A1=A8?=
=?UTF-8?q?=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Collision/ClashDetectiveIntegration.cs | 8 ++
src/PathPlanning/AutoPathFinder.cs | 36 +++++++--
src/PathPlanning/ChannelBasedGridBuilder.cs | 12 ++-
src/PathPlanning/GridMap.cs | 78 +++----------------
src/PathPlanning/GridMapGenerator.cs | 34 ++++++--
src/PathPlanning/PathOptimizer.cs | 71 +++++++++++++++--
.../ViewModels/AnimationControlViewModel.cs | 13 +++-
7 files changed, 160 insertions(+), 92 deletions(-)
diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs
index 1d2f131..05d7209 100644
--- a/src/Core/Collision/ClashDetectiveIntegration.cs
+++ b/src/Core/Collision/ClashDetectiveIntegration.cs
@@ -626,6 +626,14 @@ namespace NavisworksTransport
LogManager.Info($"=== 统计分离完成 ===");
LogManager.Info("=== 动画碰撞测试(分组方案)完成 ===");
+
+ // 触发碰撞检测完成事件,通知生成报告
+ if (validCollisions.Count > 0)
+ {
+ LogManager.Info($"触发CollisionDetected事件,通知生成报告({validCollisions.Count}个碰撞)");
+ var eventArgs = new CollisionDetectedEventArgs(validCollisions);
+ OnCollisionDetected(eventArgs);
+ }
}
}
catch (Exception ex)
diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs
index 38bd13a..330cc4e 100644
--- a/src/PathPlanning/AutoPathFinder.cs
+++ b/src/PathPlanning/AutoPathFinder.cs
@@ -704,8 +704,17 @@ namespace NavisworksTransport.PathPlanning
var endCell = gridMap.GetCell(endGridPos);
if (endCell.HasValue)
{
+ // 调试:检查门网格的Z坐标
+ if (endCell.Value.CellType == CategoryAttributeManager.LogisticsElementType.门)
+ {
+ LogManager.Info($"[路径转换-门] 门网格({endGridPos.X},{endGridPos.Y}) WorldPosition.Z={endCell.Value.WorldPosition.Z:F3}, 位置({endWorldPos.X:F2},{endWorldPos.Y:F2})");
+ }
endWorldPos = new Point3D(endWorldPos.X, endWorldPos.Y, endCell.Value.WorldPosition.Z);
}
+ else
+ {
+ LogManager.Warning($"[路径转换] 网格({endGridPos.X},{endGridPos.Y})获取失败,位置({endWorldPos.X:F2},{endWorldPos.Y:F2}),Z坐标保持为0");
+ }
// 检查是否与上一个点重复(A*可能在转弯点有重复)
bool isDuplicate = worldPath.Count > 0 &&
@@ -751,10 +760,27 @@ namespace NavisworksTransport.PathPlanning
{
var startPoint = optimizedPath[currentIndex];
var endPoint = optimizedPath[testIndex];
- var distance = Math.Sqrt(Math.Pow(endPoint.X - startPoint.X, 2) +
+ var distance = Math.Sqrt(Math.Pow(endPoint.X - startPoint.X, 2) +
Math.Pow(endPoint.Y - startPoint.Y, 2));
-
- if (IsDirectPathClear(startPoint, endPoint, gridMap))
+
+ // 🔥 新增:在尝试连接远点之前,先检查是否会跳过高度变化点
+ bool canSkip = true;
+ const double heightTolerance = 1e-6; // 与PathOptimizer保持一致
+
+ for (int checkIdx = currentIndex + 1; checkIdx < testIndex; checkIdx++)
+ {
+ // 检查被跳过的点是否有高度变化
+ if (Math.Abs(optimizedPath[checkIdx].Z - optimizedPath[checkIdx-1].Z) > heightTolerance ||
+ (checkIdx + 1 < optimizedPath.Count &&
+ Math.Abs(optimizedPath[checkIdx+1].Z - optimizedPath[checkIdx].Z) > heightTolerance))
+ {
+ canSkip = false;
+ LogManager.Debug($"[斜线优化] 不能跳过点{checkIdx},存在高度变化");
+ break;
+ }
+ }
+
+ if (canSkip && IsDirectPathClear(startPoint, endPoint, gridMap))
{
// 可以直线连接,更新最远索引
farthestIndex = testIndex;
@@ -1367,9 +1393,9 @@ namespace NavisworksTransport.PathPlanning
if (validIntervals.Any())
{
- // 选择最低的有效区间的底部高度
+ // 选择最低的有效区间的底部高度,转换为绝对坐标
var bestInterval = validIntervals.First();
- return bestInterval.MinZ;
+ return cell.WorldPosition.Z + bestInterval.MinZ;
}
}
diff --git a/src/PathPlanning/ChannelBasedGridBuilder.cs b/src/PathPlanning/ChannelBasedGridBuilder.cs
index 93b0367..b1d1ef1 100644
--- a/src/PathPlanning/ChannelBasedGridBuilder.cs
+++ b/src/PathPlanning/ChannelBasedGridBuilder.cs
@@ -221,6 +221,9 @@ namespace NavisworksTransport.PathPlanning
var minGrid = gridMap.WorldToGrid(new Point3D(minX, minY, 0));
var maxGrid = gridMap.WorldToGrid(new Point3D(maxX, maxY, 0));
+ // 计算三角形的平均高度作为通道顶面高度
+ double triangleHeight = (triangle.Point1.Z + triangle.Point2.Z + triangle.Point3.Z) / 3.0;
+
// 遍历边界框内的所有网格点
for (int x = minGrid.X; x <= maxGrid.X; x++)
{
@@ -234,7 +237,8 @@ namespace NavisworksTransport.PathPlanning
// 检查点是否在三角形内部
if (GeometryHelper.IsPointInTriangle2D(worldPos, triangle))
{
- SetCellAsChannel(gridMap, gridPos, null);
+ // 传递实际的三角形高度
+ SetCellAsChannel(gridMap, gridPos, null, triangleHeight);
}
}
}
@@ -248,10 +252,10 @@ namespace NavisworksTransport.PathPlanning
/// 网格地图
/// 网格位置
/// 关联的模型物品
- private void SetCellAsChannel(GridMap gridMap, GridPoint2D gridPos, ModelItem relatedItem)
+ private void SetCellAsChannel(GridMap gridMap, GridPoint2D gridPos, ModelItem relatedItem, double channelHeight = 0.0)
{
- // 使用GridMap提供的方法设置通道单元格
- gridMap.SetCellAsChannel(gridPos, ChannelType.Corridor, relatedItem);
+ // 使用GridMap提供的方法设置通道单元格,传递实际高度
+ gridMap.SetCellAsChannel(gridPos, ChannelType.Corridor, relatedItem, channelHeight);
}
diff --git a/src/PathPlanning/GridMap.cs b/src/PathPlanning/GridMap.cs
index 305b73b..d7a8539 100644
--- a/src/PathPlanning/GridMap.cs
+++ b/src/PathPlanning/GridMap.cs
@@ -153,8 +153,10 @@ namespace NavisworksTransport.PathPlanning
{
for (int y = 0; y < Height; y++)
{
- // 只计算2D世界坐标,Z坐标将在有规划点信息后单独设置
- var worldPos = GridToWorld2D(new GridPoint2D(x, y)); // 直接使用返回的Point3D,Z=0
+ // 计算2D世界坐标,并设置合理的初始Z坐标(使用地面高度而不是0)
+ var worldPos2D = GridToWorld2D(new GridPoint2D(x, y));
+ // 使用边界框最小Z值(地面高度)作为Unknown网格的初始高度
+ var worldPos = new Point3D(worldPos2D.X, worldPos2D.Y, Bounds.Min.Z);
var cell = new GridCell
{
@@ -209,68 +211,7 @@ namespace NavisworksTransport.PathPlanning
return new Point3D(world2D.X, world2D.Y, z);
}
- ///
- /// 线性插值Z坐标计算
- ///
- /// 世界坐标X
- /// 世界坐标Y
- /// 插值后的Z坐标
- private double CalculateInterpolatedZ(double worldX, double worldY)
- {
- // 必须先设置规划点才能进行Z坐标插值计算
- if (!HasPlanningPoints)
- {
- throw new InvalidOperationException("必须先设置PlanningStartPoint和PlanningEndPoint才能计算网格Z坐标");
- }
- var start = PlanningStartPoint;
- var end = PlanningEndPoint;
-
- // 计算当前点到起点和终点的距离
- double distanceToStart = Math.Sqrt(Math.Pow(worldX - start.X, 2) + Math.Pow(worldY - start.Y, 2));
- double distanceToEnd = Math.Sqrt(Math.Pow(worldX - end.X, 2) + Math.Pow(worldY - end.Y, 2));
- double totalDistance = Math.Sqrt(Math.Pow(end.X - start.X, 2) + Math.Pow(end.Y - start.Y, 2));
-
- // 防止除零错误
- if (totalDistance < 0.001)
- {
- return (start.Z + end.Z) / 2.0; // 返回平均高度
- }
-
- // 线性插值:根据到起点的距离比例计算Z坐标
- double ratio = distanceToStart / totalDistance;
- double interpolatedZ = start.Z + (end.Z - start.Z) * ratio;
-
- return interpolatedZ;
- }
-
- ///
- /// 更新所有网格单元格的WorldPosition.Z坐标
- /// 必须在设置PlanningStartPoint和PlanningEndPoint之后调用
- ///
- public void UpdateCellWorldPositionZ()
- {
- if (!HasPlanningPoints)
- {
- throw new InvalidOperationException("必须先设置PlanningStartPoint和PlanningEndPoint才能更新网格Z坐标");
- }
-
- for (int x = 0; x < Width; x++)
- {
- for (int y = 0; y < Height; y++)
- {
- var cell = Cells[x, y];
- var world2D = GridToWorld2D(new GridPoint2D(x, y));
- double interpolatedZ = CalculateInterpolatedZ(world2D.X, world2D.Y);
-
- // 更新单元格的WorldPosition.Z坐标
- cell.WorldPosition = new Point3D(world2D.X, world2D.Y, interpolatedZ);
- Cells[x, y] = cell;
- }
- }
-
- LogManager.Info($"[网格Z坐标更新] 已更新所有 {Width}x{Height} 个网格单元格的Z坐标");
- }
///
/// 检查网格坐标是否在有效范围内
@@ -342,13 +283,18 @@ namespace NavisworksTransport.PathPlanning
/// 网格坐标
/// 通道类型
/// 关联的模型物品
- public void SetCellAsChannel(GridPoint2D gridPosition, ChannelType channelType = ChannelType.Corridor, ModelItem relatedItem = null)
+ public void SetCellAsChannel(GridPoint2D gridPosition, ChannelType channelType = ChannelType.Corridor, ModelItem relatedItem = null, double channelHeight = 0.0)
{
if (!IsValidGridPosition(gridPosition))
return;
- var existingZ = Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z;
- var worldPos = GridToWorld3D(gridPosition, existingZ);
+ // 使用传入的通道高度,如果没有提供则使用现有的Z坐标或边界最大Z
+ double actualZ = channelHeight > 0 ? channelHeight :
+ (Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z > 0 ?
+ Cells[gridPosition.X, gridPosition.Y].WorldPosition.Z :
+ Bounds.Max.Z);
+
+ var worldPos = GridToWorld3D(gridPosition, actualZ);
var cell = new GridCell
{
IsWalkable = true,
diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs
index 4afb17f..15c8d55 100644
--- a/src/PathPlanning/GridMapGenerator.cs
+++ b/src/PathPlanning/GridMapGenerator.cs
@@ -174,8 +174,7 @@ namespace NavisworksTransport.PathPlanning
channelCoverage.GridMap.PlanningEndPoint = planningEndPoint;
channelCoverage.GridMap.HasPlanningPoints = true;
- // 9. 更新所有网格单元格的Z坐标(现在有了规划点信息)
- channelCoverage.GridMap.UpdateCellWorldPositionZ();
+ // 注意:网格的Z坐标已在通道生成时设置为实际通道高度,无需重新计算
}
// 🔥 关键修复:将通道数据设置到GridMap中,供后续PathPlanningManager使用
@@ -270,8 +269,7 @@ namespace NavisworksTransport.PathPlanning
channelCoverage.GridMap.PlanningEndPoint = planningEndPoint;
channelCoverage.GridMap.HasPlanningPoints = true;
- // 5. 更新所有网格单元格的Z坐标(现在有了规划点信息)
- channelCoverage.GridMap.UpdateCellWorldPositionZ();
+ // 注意:网格的Z坐标已在通道生成时设置为实际通道高度,无需重新计算
}
// 将通道数据设置到GridMap中,供后续PathPlanningManager使用
@@ -358,12 +356,34 @@ namespace NavisworksTransport.PathPlanning
LogManager.Debug($"【门元素处理】门元素 {doorItem.DisplayName} 限高: {doorHeight:F2} 模型单位");
}
- // 设置开口区域的网格为可通行的门类型,使用正确的高度
+ // 获取门底部的Z坐标
+ double doorBottomZ = bbox.Min.Z;
+
+ // 设置开口区域的网格为可通行的门类型
foreach (var (x, y) in coveredCells)
{
var gridPos = new GridPoint2D(x, y);
- // 使用正确的门高度设置网格
- gridMap.SetCell(gridPos, true, doorHeight, CategoryAttributeManager.LogisticsElementType.门);
+ // 设置门网格,cost传0让SetCell自动调用GetCost()计算(门的默认cost是1.2)
+ gridMap.SetCell(gridPos, true, 0, CategoryAttributeManager.LogisticsElementType.门);
+
+ // 由于GridCell是结构体,需要重新获取、修改、再设置回去
+ var cell = gridMap.Cells[x, y];
+
+ // 更新门网格的Z坐标为门底部位置
+ var worldPos = gridMap.GridToWorld3D(gridPos, doorBottomZ);
+ cell.WorldPosition = worldPos;
+
+ // 将门的高度范围存储在PassableHeights中供后续使用
+ if (doorHeight > 0)
+ {
+ cell.PassableHeights = new List
+ {
+ new HeightInterval(0, doorHeight)
+ };
+ }
+
+ // 将修改后的结构体设置回数组
+ gridMap.Cells[x, y] = cell;
}
processedCount++;
diff --git a/src/PathPlanning/PathOptimizer.cs b/src/PathPlanning/PathOptimizer.cs
index cb11a46..cdb049c 100644
--- a/src/PathPlanning/PathOptimizer.cs
+++ b/src/PathPlanning/PathOptimizer.cs
@@ -174,24 +174,36 @@ namespace NavisworksTransport.PathPlanning
initialDy == 0 ? 0 : Math.Sign(initialDy)
);
- // 简化路径:只保留转弯点(使用归一化方向)
+ // 简化路径:只保留转弯点和高度变化点(使用归一化方向)
for (int i = 2; i < dedupedGridPath.Count; i++)
{
// 计算当前线段的位移
int dx = dedupedGridPath[i].X - dedupedGridPath[i-1].X;
int dy = dedupedGridPath[i].Y - dedupedGridPath[i-1].Y;
-
+
// 归一化为单位方向向量 (-1, 0, 1)
var currentDirection = new GridPoint2D(
dx == 0 ? 0 : Math.Sign(dx),
dy == 0 ? 0 : Math.Sign(dy)
);
-
- // 只有真正的方向改变时才保留转弯点
- if (!currentDirection.Equals(prevDirection))
+
+ // 检查高度变化
+ bool hasHeightChange = HasHeightChange(
+ dedupedPoints[i-2],
+ dedupedPoints[i-1],
+ dedupedPoints[i]
+ );
+
+ // 只有真正的方向改变或高度变化时才保留转折点
+ if (!currentDirection.Equals(prevDirection) || hasHeightChange)
{
simplified.Add(dedupedGridPath[i - 1]);
prevDirection = currentDirection;
+
+ if (hasHeightChange)
+ {
+ LogManager.Debug($"[网格路径优化] 保留高度变化点 {i-1}");
+ }
}
}
@@ -257,6 +269,51 @@ namespace NavisworksTransport.PathPlanning
return simplified;
}
+ ///
+ /// 检查两个路径点之间是否有显著的高度变化
+ /// 用于路径优化时判断是否需要保留中间点
+ ///
+ /// 第一个路径点
+ /// 第二个路径点
+ /// 如果有高度变化返回true,需要保留中间点
+ private bool HasHeightChange(PathPoint point1, PathPoint point2)
+ {
+ const double heightTolerance = 1e-6; // 仅覆盖浮点数精度误差
+ return Math.Abs(point1.Position.Z - point2.Position.Z) > heightTolerance;
+ }
+
+ ///
+ /// 检查三个点之间是否有高度变化(用于判断是否可以跳过中间点)
+ ///
+ /// 第一个点的3D坐标
+ /// 中间点的3D坐标
+ /// 第三个点的3D坐标
+ /// 如果有高度变化返回true,不能跳过中间点
+ private bool HasHeightChange(Point3D p1, Point3D p2, Point3D p3)
+ {
+ const double heightTolerance = 1e-6; // 仅覆盖浮点数精度误差
+
+ // 检查任意两点间是否有高度变化
+ if (Math.Abs(p1.Z - p2.Z) > heightTolerance ||
+ Math.Abs(p2.Z - p3.Z) > heightTolerance)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 检查三个点之间是否有高度变化(PathPoint版本)
+ ///
+ /// 第一个路径点
+ /// 中间路径点
+ /// 第三个路径点
+ /// 如果有高度变化返回true,不能跳过中间点
+ private bool HasHeightChange(PathPoint point1, PathPoint point2, PathPoint point3)
+ {
+ return HasHeightChange(point1.Position, point2.Position, point3.Position);
+ }
+
///
/// 检查三点是否真正共线(只有当三点都在同一条直线上且方向一致时才返回true)
/// 🔥 关键修复:严格检查两个线段的方向是否一致,防止在转折点处错误合并
@@ -269,8 +326,8 @@ namespace NavisworksTransport.PathPlanning
{
const double tolerance = 0.1; // 容差值,单位:模型单位
- // 🔥 修复:首先检查Z坐标,确保是2D路径
- if (Math.Abs(p1.Z - p2.Z) > tolerance || Math.Abs(p2.Z - p3.Z) > tolerance)
+ // 🔥 修复:使用统一的高度检查函数
+ if (HasHeightChange(p1, p2, p3))
{
LogManager.Debug($"[共线检测] ❌ 拒绝Z方向变化:({p1.X:F3},{p1.Y:F3},{p1.Z:F3}) -> ({p2.X:F3},{p2.Y:F3},{p2.Z:F3}) -> ({p3.X:F3},{p3.Y:F3},{p3.Z:F3})");
return false;
diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
index 1be3d14..6b75999 100644
--- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
+++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
@@ -430,8 +430,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 初始化命令
InitializeCommands();
- // 订阅碰撞检测事件
- _clashIntegration.CollisionDetected += OnCollisionDetected;
+ // 移除重复的碰撞检测事件订阅,只在带参构造函数中保留
+ // _clashIntegration.CollisionDetected += OnCollisionDetected;
LogManager.Info("AnimationControlViewModel初始化完成(含缓存管理)");
}
@@ -1039,11 +1039,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
HasCollisionResults = e.Results.Count > 0;
- var summary = e.Results.Count > 0
+ var summary = e.Results.Count > 0
? $"发现 {e.Results.Count} 个碰撞点"
: "未发现碰撞";
UpdateMainStatus(summary);
});
+
+ // 自动生成并显示碰撞报告
+ if (HasCollisionResults)
+ {
+ LogManager.Info("碰撞检测完成,自动生成报告");
+ await ExecuteViewCollisionReportAsync();
+ }
}
///