Fix YUp hoisting path rendering and finish sync

This commit is contained in:
tian 2026-03-22 12:21:25 +08:00
parent 042b9a2804
commit 8f55b5e9a1
10 changed files with 713 additions and 271 deletions

View File

@ -58,6 +58,7 @@
<Compile Include="UnitTests\CoordinateSystem\CanonicalPlanarPoseBuilderTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\CanonicalRailOffsetResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\CanonicalTrackedPositionResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\HoistingCoordinateHelperTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\ModelAxisConventionTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\ProjectReferenceFrameTests.cs" />
<Compile Include="UnitTests\Properties\AssemblyInfo.cs" />

View File

@ -337,6 +337,7 @@
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailPoseBuilder.cs" />
<Compile Include="src\Utils\CoordinateSystem\CanonicalTrackedPositionResolver.cs" />
<Compile Include="src\Utils\CoordinateSystem\HostCoordinateAdapter.cs" />
<Compile Include="src\Utils\CoordinateSystem\HoistingCoordinateHelper.cs" />
<Compile Include="src\Utils\CoordinateSystem\LocalAxisDirection.cs" />
<Compile Include="src\Utils\CoordinateSystem\ModelAxisConvention.cs" />
<Compile Include="src\Utils\CoordinateSystem\ProjectReferenceFrame.cs" />

View File

@ -0,0 +1,146 @@
using Autodesk.Navisworks.Api;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.CoordinateSystem;
using System.Numerics;
namespace NavisworksTransport.UnitTests.CoordinateSystem
{
[TestClass]
public class HoistingCoordinateHelperTests
{
[TestMethod]
public void OffsetAlongUp_YUp_ShouldChangeHostYOnly()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
var startPoint = new Vector3(1.0f, 2.0f, 3.0f);
Vector3 liftedPoint = HoistingCoordinateHelper.OffsetAlongUp3(startPoint, 10.0, adapter);
AssertPoint(liftedPoint, 1.0, 12.0, 3.0);
}
[TestMethod]
public void ProjectToAerialElevation_YUp_ShouldKeepGroundHorizontalCoords()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
var groundPoint = new Vector3(4.0f, 5.0f, 6.0f);
var aerialReferencePoint = new Vector3(1.0f, 12.0f, 3.0f);
Vector3 projectedPoint = HoistingCoordinateHelper.ProjectToAerialElevation3(groundPoint, aerialReferencePoint, adapter);
AssertPoint(projectedPoint, 4.0, 12.0, 6.0);
}
[TestMethod]
public void RelativeHeight_YUp_ShouldUseHostYDifference()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
var groundPoint = new Vector3(-10.0f, -30.0f, 8.0f);
var aerialPoint = new Vector3(-10.0f, 35.0f, 8.0f);
double relativeHeight = HoistingCoordinateHelper.GetRelativeHeight3(groundPoint, aerialPoint, adapter);
Assert.AreEqual(65.0, relativeHeight, 1e-9);
}
[TestMethod]
public void SetElevation_YUp_ShouldRestoreClickedGroundElevationWithoutChangingHorizontalProjection()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
var aerialPoint = new Vector3(-180.50f, 28.17f, 14.83f);
var clickedGroundPoint = new Vector3(-180.50f, -12.40f, 14.83f);
Vector3 landingPoint = HoistingCoordinateHelper.SetElevation3(
aerialPoint,
HoistingCoordinateHelper.GetElevation(clickedGroundPoint, adapter),
adapter);
AssertPoint(landingPoint, -180.50, -12.40, 14.83);
}
[TestMethod]
public void CreateHorizontalTurnPoint_YUp_ShouldOperateInHostXZPlane()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
var currentPoint = new Vector3(-168.70f, 28.17f, -30.01f);
var nextPoint = new Vector3(-180.50f, 28.17f, 14.83f);
Vector3 turnPointKeepCurrentX = HoistingCoordinateHelper.CreateHorizontalTurnPoint3(
currentPoint,
nextPoint,
keepCurrentFirstHorizontalAxis: true,
adapter: adapter);
Vector3 turnPointKeepCurrentZ = HoistingCoordinateHelper.CreateHorizontalTurnPoint3(
currentPoint,
nextPoint,
keepCurrentFirstHorizontalAxis: false,
adapter: adapter);
AssertPoint(turnPointKeepCurrentX, -168.70, 28.17, 14.83);
AssertPoint(turnPointKeepCurrentZ, -180.50, 28.17, -30.01);
}
[TestMethod]
public void GetHorizontalDirection_YUp_ShouldProjectToHostXZPlane()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
var fromPoint = new Vector3(-164.81f, 45.45f, 74.69f);
var toPoint = new Vector3(-176.90f, 45.45f, 40.48f);
Vector3 horizontalDirection = HoistingCoordinateHelper.GetHorizontalDirection3(fromPoint, toPoint, adapter);
Assert.AreEqual(0.0, horizontalDirection.Y, 1e-5);
Assert.AreEqual(1.0, horizontalDirection.Length(), 1e-5);
Assert.IsTrue(horizontalDirection.X < 0.0f);
Assert.IsTrue(horizontalDirection.Z < 0.0f);
}
[TestMethod]
public void ExtendVerticalTransitionForObjectSpace_YUp_ShouldMoveLowerPointAlongHostY()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
var lowerPoint = new Vector3(-191.56f, 29.05f, -2.63f);
Vector3 extendedPoint = HoistingCoordinateHelper.ExtendVerticalTransitionForObjectSpace3(
lowerPoint,
4.0,
isLowerPoint: true,
adapter: adapter);
AssertPoint(extendedPoint, -191.56, 25.05, -2.63);
}
[TestMethod]
public void ExtendVerticalTransitionForObjectSpace_YUp_ShouldNotChangeHigherPoint()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
var higherPoint = new Vector3(-191.56f, 45.45f, -2.63f);
Vector3 unchangedPoint = HoistingCoordinateHelper.ExtendVerticalTransitionForObjectSpace3(
higherPoint,
4.0,
isLowerPoint: false,
adapter: adapter);
AssertPoint(unchangedPoint, -191.56, 45.45, -2.63);
}
[TestMethod]
public void OffsetAlongUp_ZUp_ShouldChangeHostZOnly()
{
var adapter = new HostCoordinateAdapter(CoordinateSystemType.ZUp);
var startPoint = new Vector3(1.0f, 2.0f, 3.0f);
Vector3 liftedPoint = HoistingCoordinateHelper.OffsetAlongUp3(startPoint, 10.0, adapter);
AssertPoint(liftedPoint, 1.0, 2.0, 13.0);
}
private static void AssertPoint(Vector3 actual, double x, double y, double z)
{
Assert.AreEqual(x, actual.X, 1e-5);
Assert.AreEqual(y, actual.Y, 1e-5);
Assert.AreEqual(z, actual.Z, 1e-5);
}
}
}

View File

@ -1,6 +1,6 @@
# 当前工程状态
更新时间2026-03-21
更新时间2026-03-22
## 1. 当前稳定状态
@ -79,6 +79,20 @@
- 地面/吊装路径已经开始走完整姿态链路,不再允许悄悄退回旧 `yaw` 方案。
- 如果完整姿态生成失败,应直接暴露错误,而不是 fallback。
- `YUp` 吊装路径创建主链路已补齐到宿主坐标适配架构:
- 提升、水平移动、下降、终点落地都不能再把世界 `Z` 硬编码成“向上”。
- 终点必须使用用户最后一次点击的地面点,不能回填起点地面高程。
- 吊装路径“设为终点直接结束”如果出现:
- 核心路径点数正确,但路径列表/路径点列表仍显示为 `0`
- 切到别的路径再切回来后才恢复正常
- 这类问题优先检查 `UIStateManager` 的 UI 队列消费,而不是先怀疑路径数据或坐标系。
- 本次真实根因已经确认:
- `FinishEditing()` 期间会连续追加 `CurrentRouteChanged / PathPointsListUpdated / RouteGenerated` 等 UI 事件。
- 旧的 `UIStateManager.ProcessQueuedUpdates()` 只消费“当前这一批”队列项,执行过程中新增的后续事件会滞留。
- 所以路径数据其实已经完成,只是 UI 直到下一次路径切换时才把滞留事件顺带处理掉。
- 当前修复原则:
- `UIStateManager` 必须持续消费后续批次,直到 UI 队列真正为空。
- 不要用“手动切换路径”“先选空再选回”“强行补 OnPropertyChanged”这类 UI 和稀泥方式掩盖队列问题。
## 5. 当前保留的日志策略

View File

@ -9,6 +9,7 @@ using NavisworksTransport.Utils;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Config;
using NavisworksTransport.Commands;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport
{
@ -1708,18 +1709,22 @@ namespace NavisworksTransport
// 计算下一个索引
int nextIndex = CurrentRoute.Points.Count;
// 创建下降点:在落地点正上方,Z坐标为最后一个路径点的Z保持当前高度
// 创建下降点:在落地点正上方,保持当前空中高程(沿宿主 up
var descendPoint = new PathPoint(
new Point3D(lastPoint.Position.X, lastPoint.Position.Y, lastPoint.Position.Z),
lastPoint.Position,
"下降点",
PathPointType.WayPoint);
descendPoint.Direction = HoistingPointDirection.Vertical;
descendPoint.Index = nextIndex++;
LogManager.Info($"[吊装路径] 已生成下降点(在终点正上方): ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})");
// 创建落地点终点使用最后一个路径点的XY使用起点Z作为地面高度
Point3D finalGroundPoint = _hoistingGroundIntermediatePoints.Count > 0
? _hoistingGroundIntermediatePoints[_hoistingGroundIntermediatePoints.Count - 1]
: SetHoistingElevation(lastPoint.Position, GetHoistingElevation(startPoint.Position));
// 创建落地点(终点):使用用户最后一次点击的地面点,避免把终点高程错误吸回起点
var endPoint = new PathPoint(
new Point3D(lastPoint.Position.X, lastPoint.Position.Y, startPoint.Position.Z),
finalGroundPoint,
"落地点",
PathPointType.EndPoint);
endPoint.Direction = HoistingPointDirection.Vertical;
@ -1796,6 +1801,9 @@ namespace NavisworksTransport
// 触发路径点列表更新事件
RaisePathPointsListUpdated(CurrentRoute, "编辑完成");
// 结束编辑后主动再同步一次当前路径确保UI中的占位路径能回填完整点列表
SetCurrentRouteInternal(CurrentRoute, triggerEvent: true);
// 触发路径生成事件
RaiseRouteGenerated(CurrentRoute, RouteGenerationMethod.Manual);
@ -1910,12 +1918,12 @@ namespace NavisworksTransport
{
var startPoint = points[0];
var liftPoint = points[1];
double newAerialZ = startPoint.Position.Z + route.LiftHeight;
liftPoint.Position = new Point3D(startPoint.Position.X, startPoint.Position.Y, newAerialZ);
double newAerialElevation = GetHoistingElevation(startPoint.Position) + route.LiftHeight;
liftPoint.Position = SetHoistingElevation(startPoint.Position, newAerialElevation);
// 同步所有其他空中点Z坐标索引2到n-2
for (int i = 2; i <= points.Count - 2; i++)
{
points[i].Position = new Point3D(points[i].Position.X, points[i].Position.Y, newAerialZ);
points[i].Position = SetHoistingElevation(points[i].Position, newAerialElevation);
}
LogManager.Debug($"[吊装路径] 修改起点,更新提升点位置: ({liftPoint.Position.X:F2}, {liftPoint.Position.Y:F2}, {liftPoint.Position.Z:F2})同步所有空中点Z坐标");
}
@ -1924,7 +1932,7 @@ namespace NavisworksTransport
{
var endPoint = points[modifiedPointIndex];
var descendPoint = points[modifiedPointIndex - 1];
descendPoint.Position = new Point3D(endPoint.Position.X, endPoint.Position.Y, descendPoint.Position.Z);
descendPoint.Position = SetHoistingElevation(endPoint.Position, GetHoistingElevation(descendPoint.Position));
LogManager.Debug($"[吊装路径] 修改终点,更新下降点位置: ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})");
}
}
@ -1951,9 +1959,12 @@ namespace NavisworksTransport
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})");
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);
Point3D canonicalCurrent = ToHoistingCanonicalPoint(currentPoint.Position);
Point3D canonicalNext = ToHoistingCanonicalPoint(nextPoint.Position);
double deltaX = Math.Abs(canonicalNext.X - canonicalCurrent.X);
double deltaY = Math.Abs(canonicalNext.Y - canonicalCurrent.Y);
double deltaZ = Math.Abs(canonicalNext.Z - canonicalCurrent.Z);
LogManager.Debug($"[斜线处理] deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2}");
@ -2010,7 +2021,7 @@ namespace NavisworksTransport
// 不修改用户点击的坐标,只插入转折点
// 转折点X用新点的XY用旧点的Y先X方向移动再Y方向移动
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z),
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Longitudinal),
HoistingPointDirection.Longitudinal);
}
@ -2024,7 +2035,7 @@ namespace NavisworksTransport
// 插入中间点先水平移动到新X旧Z目标点自然就是新X新Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z),
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Longitudinal),
HoistingPointDirection.Longitudinal);
}
@ -2038,7 +2049,7 @@ namespace NavisworksTransport
// 插入中间点先水平移动到新Y旧Z目标点自然就是新Y新Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(currentPoint.Position.X, nextPoint.Position.Y, currentPoint.Position.Z),
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Lateral),
HoistingPointDirection.Lateral);
}
@ -2060,7 +2071,7 @@ namespace NavisworksTransport
{
// 插入中间点先X方向移动保持Y到目标X后再处理Y和Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z),
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Longitudinal),
HoistingPointDirection.Longitudinal);
}
else
@ -2070,13 +2081,13 @@ namespace NavisworksTransport
if (deltaX > deltaY)
{
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(nextPoint.Position.X, currentPoint.Position.Y, currentPoint.Position.Z),
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Longitudinal),
HoistingPointDirection.Longitudinal);
}
else
{
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
new Point3D(currentPoint.Position.X, nextPoint.Position.Y, currentPoint.Position.Z),
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Lateral),
HoistingPointDirection.Lateral);
}
}
@ -2182,10 +2193,12 @@ namespace NavisworksTransport
continue;
}
// 判断三个点是否在同一直线上需要在3D空间中共线
// 首先检查Z坐标是否相同在同一平面上
double deltaZ1 = Math.Abs(currentPoint.Position.Z - prevPoint.Position.Z);
double deltaZ2 = Math.Abs(nextPoint.Position.Z - currentPoint.Position.Z);
// 判断三个点是否在同一直线上(统一在 Canonical 水平面上判断)
Point3D canonicalPrevPoint = ToHoistingCanonicalPoint(prevPoint.Position);
Point3D canonicalCurrentPoint = ToHoistingCanonicalPoint(currentPoint.Position);
Point3D canonicalNextPoint = ToHoistingCanonicalPoint(nextPoint.Position);
double deltaZ1 = Math.Abs(canonicalCurrentPoint.Z - canonicalPrevPoint.Z);
double deltaZ2 = Math.Abs(canonicalNextPoint.Z - canonicalCurrentPoint.Z);
bool sameZ = deltaZ1 < tolerance && deltaZ2 < tolerance;
// 如果Z不同这三个点一定不共线一个是垂直过渡点
@ -2196,10 +2209,10 @@ namespace NavisworksTransport
}
// 在同一平面上检查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 &&
Math.Abs(currentPoint.Position.Y - nextPoint.Position.Y) < tolerance;
bool sameX = Math.Abs(canonicalPrevPoint.X - canonicalCurrentPoint.X) < tolerance &&
Math.Abs(canonicalCurrentPoint.X - canonicalNextPoint.X) < tolerance;
bool sameY = Math.Abs(canonicalPrevPoint.Y - canonicalCurrentPoint.Y) < tolerance &&
Math.Abs(canonicalCurrentPoint.Y - canonicalNextPoint.Y) < tolerance;
bool isCollinear = sameX || sameY;
@ -2267,7 +2280,7 @@ namespace NavisworksTransport
var layers = new Dictionary<double, List<int>>();
for (int i = 1; i < points.Count - 1; i++) // 跳过起点(0)和终点(Count-1)
{
double z = points[i].Position.Z;
double z = GetHoistingElevation(points[i].Position);
// 查找是否已有相同Z坐标的层考虑浮点数精度
bool found = false;
@ -2352,7 +2365,7 @@ namespace NavisworksTransport
}
bool hasChanges = false;
double layerZ = points[layerIndices[0]].Position.Z;
double layerZ = GetHoistingElevation(points[layerIndices[0]].Position);
// 在该层内进行环路检测
// n和m是layerIndices列表中的索引位置
@ -2374,10 +2387,10 @@ namespace NavisworksTransport
var p4 = points[idx4].Position;
// 确保所有点都在同一层水平线段Z坐标严格相同只考虑浮点数精度
if (Math.Abs(p1.Z - layerZ) > epsilon ||
Math.Abs(p2.Z - layerZ) > epsilon ||
Math.Abs(p3.Z - layerZ) > epsilon ||
Math.Abs(p4.Z - layerZ) > epsilon)
if (Math.Abs(GetHoistingElevation(p1) - layerZ) > epsilon ||
Math.Abs(GetHoistingElevation(p2) - layerZ) > epsilon ||
Math.Abs(GetHoistingElevation(p3) - layerZ) > epsilon ||
Math.Abs(GetHoistingElevation(p4) - layerZ) > epsilon)
{
m++;
if (n + m + 1 >= layerIndices.Count)
@ -2476,14 +2489,19 @@ namespace NavisworksTransport
private bool GetOrthogonalSegmentIntersection(Point3D a1, Point3D a2, Point3D b1, Point3D b2, out Point3D intersection)
{
intersection = new Point3D(0, 0, 0);
// 判断线段A是水平还是垂直
bool aIsHorizontal = Math.Abs(a1.Y - a2.Y) < 0.001;
bool aIsVertical = Math.Abs(a1.X - a2.X) < 0.001;
Point3D canonicalA1 = ToHoistingCanonicalPoint(a1);
Point3D canonicalA2 = ToHoistingCanonicalPoint(a2);
Point3D canonicalB1 = ToHoistingCanonicalPoint(b1);
Point3D canonicalB2 = ToHoistingCanonicalPoint(b2);
// 判断线段A是水平还是垂直在 Canonical 水平面 XY 中)
bool aIsHorizontal = Math.Abs(canonicalA1.Y - canonicalA2.Y) < 0.001;
bool aIsVertical = Math.Abs(canonicalA1.X - canonicalA2.X) < 0.001;
// 判断线段B是水平还是垂直
bool bIsHorizontal = Math.Abs(b1.Y - b2.Y) < 0.001;
bool bIsVertical = Math.Abs(b1.X - b2.X) < 0.001;
bool bIsHorizontal = Math.Abs(canonicalB1.Y - canonicalB2.Y) < 0.001;
bool bIsVertical = Math.Abs(canonicalB1.X - canonicalB2.X) < 0.001;
// 必须一条水平一条垂直才可能形成矩形环路
if ((aIsHorizontal && bIsHorizontal) || (aIsVertical && bIsVertical))
@ -2495,9 +2513,9 @@ namespace NavisworksTransport
if (aIsVertical && bIsHorizontal)
{
// 交换
var temp1 = a1; var temp2 = a2;
a1 = b1; a2 = b2;
b1 = temp1; b2 = temp2;
var temp1 = canonicalA1; var temp2 = canonicalA2;
canonicalA1 = canonicalB1; canonicalA2 = canonicalB2;
canonicalB1 = temp1; canonicalB2 = temp2;
aIsHorizontal = true; aIsVertical = false;
bIsHorizontal = false; bIsVertical = true;
}
@ -2510,12 +2528,12 @@ namespace NavisworksTransport
// 水平线段A: y = a1.Y, x范围 [min(a1.X,a2.X), max(a1.X,a2.X)]
// 垂直线段B: x = b1.X, y范围 [min(b1.Y,b2.Y), max(b1.Y,b2.Y)]
double aY = a1.Y;
double bX = b1.X;
double aMinX = Math.Min(a1.X, a2.X);
double aMaxX = Math.Max(a1.X, a2.X);
double bMinY = Math.Min(b1.Y, b2.Y);
double bMaxY = Math.Max(b1.Y, b2.Y);
double aY = canonicalA1.Y;
double bX = canonicalB1.X;
double aMinX = Math.Min(canonicalA1.X, canonicalA2.X);
double aMaxX = Math.Max(canonicalA1.X, canonicalA2.X);
double bMinY = Math.Min(canonicalB1.Y, canonicalB2.Y);
double bMaxY = Math.Max(canonicalB1.Y, canonicalB2.Y);
// 检查垂直线段的X是否在水平线段的X范围内不包括端点
// 且水平线段的Y是否在垂直线段的Y范围内不包括端点
@ -2526,7 +2544,10 @@ namespace NavisworksTransport
if (xInRange && yInRange)
{
intersection = new Point3D(bX, aY, a1.Z);
intersection = GetHoistingHostAdapter().FromCanonicalPoint(new Point3D(
bX,
aY,
canonicalA1.Z));
return true;
}
@ -2642,16 +2663,12 @@ namespace NavisworksTransport
if (isLiftPoint || isDescendPoint)
{
// 修改提升点或下降点的Z坐标同步更新所有空中路径点的Z坐标
double newHoistingHeight = newPosition.Z;
double originalZ = pointToUpdate.Position.Z;
double newHoistingHeight = GetHoistingElevation(newPosition);
double originalZ = GetHoistingElevation(pointToUpdate.Position);
double heightDelta = newHoistingHeight - originalZ;
// 更新当前点的XY和Z坐标
pointToUpdate.Position = new Point3D(
newPosition.X,
newPosition.Y,
newHoistingHeight
);
// 更新当前点的宿主水平位置与高程
pointToUpdate.Position = SetHoistingElevation(newPosition, newHoistingHeight);
// 同步更新所有空中路径点的Z坐标
foreach (var point in route.Points)
@ -2660,11 +2677,7 @@ namespace NavisworksTransport
// 跳过当前正在修改的点,避免重复更新
if (point.Name != "起吊点" && point.Name != "落地点" && point != pointToUpdate)
{
point.Position = new Point3D(
point.Position.X,
point.Position.Y,
point.Position.Z + heightDelta
);
point.Position = OffsetPointAlongHoistingUp(point.Position, heightDelta);
}
}
@ -2679,12 +2692,8 @@ namespace NavisworksTransport
// 空中路径点非提升点、下降点只更新XY坐标保持原有吊装高度
else if (!isStartPoint && !isEndPoint)
{
double originalZ = pointToUpdate.Position.Z;
pointToUpdate.Position = new Point3D(
newPosition.X,
newPosition.Y,
originalZ // 保留原有Z坐标吊装高度
);
double originalZ = GetHoistingElevation(pointToUpdate.Position);
pointToUpdate.Position = SetHoistingElevation(newPosition, originalZ);
LogManager.Info($"[更新路径点] 吊装路径空中点 {pointToUpdate.Name},保留原有高度: {originalZ:F3}");
}
// 起吊点/落地点:更新完整位置
@ -2739,6 +2748,41 @@ namespace NavisworksTransport
/// </summary>
/// <param name="route">吊装路径</param>
/// <returns>提升高度(从起吊点到提升点的高度差)</returns>
private HostCoordinateAdapter GetHoistingHostAdapter()
{
return CoordinateSystemManager.Instance.CreateHostAdapter();
}
private Point3D ToHoistingCanonicalPoint(Point3D hostPoint)
{
return GetHoistingHostAdapter().ToCanonicalPoint(hostPoint);
}
private double GetHoistingElevation(Point3D point)
{
return HoistingCoordinateHelper.GetElevation(point, GetHoistingHostAdapter());
}
private Point3D SetHoistingElevation(Point3D point, double elevation)
{
return HoistingCoordinateHelper.SetElevation(point, elevation, GetHoistingHostAdapter());
}
private Point3D OffsetPointAlongHoistingUp(Point3D point, double offset)
{
return HoistingCoordinateHelper.OffsetAlongUp(point, offset, GetHoistingHostAdapter());
}
private Point3D CreateHorizontalTurnPoint(Point3D currentPoint, Point3D nextPoint, HoistingPointDirection direction)
{
bool keepCurrentFirstHorizontalAxis = direction == HoistingPointDirection.Lateral;
return HoistingCoordinateHelper.CreateHorizontalTurnPoint(
currentPoint,
nextPoint,
keepCurrentFirstHorizontalAxis,
GetHoistingHostAdapter());
}
private double CalculateHoistingHeight(PathRoute route)
{
if (route == null || route.PathType != PathType.Hoisting || route.Points.Count < 2)
@ -2755,8 +2799,8 @@ namespace NavisworksTransport
return 0.0;
}
// 计算高度差(提升点Z - 起吊点Z
return liftPoint.Position.Z - startPoint.Position.Z;
// 计算高度差(沿宿主 up
return GetHoistingElevation(liftPoint.Position) - GetHoistingElevation(startPoint.Position);
}
/// <summary>
@ -3209,13 +3253,14 @@ namespace NavisworksTransport
double liftHeightModelUnits = CurrentRoute.LiftHeight > 0
? CurrentRoute.LiftHeight
: UnitsConverter.ConvertFromMeters(3.0);
double targetElevation = GetHoistingElevation(CurrentRoute.Points[0].Position) + liftHeightModelUnits;
// 使用 AerialPathGenerator 生成空中路径点
var generator = new AerialPathGenerator();
confirmPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
_previewPoint.Position, // 用户点击的地面位置
liftHeightModelUnits, // 直接使用模型单位
targetElevation,
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
LogManager.Info($"[预览点-吊装路径] 已生成空中路径点: {confirmPoint.Name}, 位置: ({confirmPoint.Position.X:F2}, {confirmPoint.Position.Y:F2}, {confirmPoint.Position.Z:F2})");
@ -3908,7 +3953,7 @@ namespace NavisworksTransport
// 使用已转换的模型单位高度
var liftPoint = new PathPoint(
new Point3D(_hoistingStartPoint.X, _hoistingStartPoint.Y, _hoistingStartPoint.Z + liftHeightModelUnits),
OffsetPointAlongHoistingUp(_hoistingStartPoint, liftHeightModelUnits),
"提升点",
PathPointType.WayPoint);
liftPoint.Direction = HoistingPointDirection.Vertical;
@ -3995,30 +4040,26 @@ namespace NavisworksTransport
{
previousPoint = CurrentRoute.Points.Last().Position;
var lastPoint = CurrentRoute.Points.Last();
LogManager.Info($"[吊装模式] 上一路径点: {lastPoint.Name}, Z={lastPoint.Position.Z:F2}");
LogManager.Info($"[吊装模式] 上一路径点: {lastPoint.Name}, Elevation={GetHoistingElevation(lastPoint.Position):F2}");
}
else
{
previousPoint = new Point3D(
_hoistingStartPoint.X,
_hoistingStartPoint.Y,
_hoistingStartPoint.Z + liftHeightModelUnits);
LogManager.Info($"[吊装模式] 无上一路径点,使用起点+高度: Z={previousPoint.Z:F2}");
previousPoint = OffsetPointAlongHoistingUp(_hoistingStartPoint, liftHeightModelUnits);
LogManager.Info($"[吊装模式] 无上一路径点,使用起点+高度: Elevation={GetHoistingElevation(previousPoint):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);
// 检查高度是否变化(比较沿宿主 up 的绝对高程)
double newAbsoluteHeight = GetHoistingElevation(_hoistingStartPoint) + liftHeightModelUnits;
double heightDiff = Math.Abs(newAbsoluteHeight - GetHoistingElevation(previousPoint));
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}");
LogManager.Info($"[吊装模式] 新绝对高度={newAbsoluteHeight:F2}, 上一位置Elevation={GetHoistingElevation(previousPoint):F2}");
// 步骤1水平移动点在旧高度上移动到新XY
// 使用 AerialPathGenerator 生成水平路径点
@ -4031,7 +4072,7 @@ namespace NavisworksTransport
var aerialPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
groundPoint,
previousPoint.Z, // 保持旧高度
GetHoistingElevation(previousPoint),
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
if (aerialPoint != null)
@ -4053,12 +4094,9 @@ namespace NavisworksTransport
if (heightChanged)
{
// 高度变化:垂直上升到新高度
LogManager.Info($"[吊装模式] 高度变化,插入垂直过渡点: Z={previousPoint.Z:F2} -> Z={newAbsoluteHeight:F2}");
LogManager.Info($"[吊装模式] 高度变化,插入垂直过渡点: Elevation={GetHoistingElevation(previousPoint):F2} -> Elevation={newAbsoluteHeight:F2}");
var verticalTransitionPoint = new Point3D(
previousPoint.X,
previousPoint.Y,
newAbsoluteHeight);
var verticalTransitionPoint = SetHoistingElevation(previousPoint, newAbsoluteHeight);
var verticalPoint = new PathPoint(
verticalTransitionPoint,
$"高度过渡{CurrentRoute.Points.Count}",

View File

@ -1859,16 +1859,11 @@ namespace NavisworksTransport
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting &&
sortedPoints.Count >= 3)
{
// 吊装路径线段1是水平段从点1到点2
var horizontalStart = sortedPoints[1].Position;
var horizontalEnd = sortedPoints[2].Position;
var dx = horizontalEnd.X - horizontalStart.X;
var dy = horizontalEnd.Y - horizontalStart.Y;
var length = Math.Sqrt(dx * dx + dy * dy);
if (length > 0.001)
{
horizontalDirection = new Vector3D(dx / length, dy / length, 0);
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
horizontalDirection = ToVector3D(HoistingCoordinateHelper.GetHorizontalDirection3(
ToVector3(sortedPoints[1].Position),
ToVector3(sortedPoints[2].Position),
adapter));
}
// 直接使用路径点构建直线连线
@ -1899,13 +1894,16 @@ namespace NavisworksTransport
int firstSegment = 0; // 起吊段
int lastSegment = totalSegments - 1; // 下降段
// 检测Z方向变化来判断垂直段
double dz = endPoint.Position.Z - startPoint.Position.Z;
// 沿宿主 up 判断垂直段,避免把世界 Z 误当成向上轴
var hostUp = GetHostUpVector();
double dx = endPoint.Position.X - startPoint.Position.X;
double dy = endPoint.Position.Y - startPoint.Position.Y;
double horizontalDist = Math.Sqrt(dx * dx + dy * dy);
// 如果Z变化占主导垂直段或者是第一个/最后一个线段
bool isZDominant = Math.Abs(dz) > horizontalDist * 2.0; // 垂直变化是水平变化的2倍以上
double dz = endPoint.Position.Z - startPoint.Position.Z;
double upDelta = dx * hostUp.X + dy * hostUp.Y + dz * hostUp.Z;
double segmentLength = Math.Sqrt(dx * dx + dy * dy + dz * dz);
double horizontalDist = Math.Sqrt(Math.Max(0.0, segmentLength * segmentLength - upDelta * upDelta));
// 如果沿宿主 up 的变化占主导(垂直段),或者是第一个/最后一个线段
bool isZDominant = Math.Abs(upDelta) > horizontalDist * 2.0;
// 第一个线段和最后一个线段是起吊/下降段
// 中间的垂直段是层间过渡
@ -1920,15 +1918,22 @@ namespace NavisworksTransport
bool isTurn90Horizontal = false;
if (!isVerticalSegment && visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting && horizontalDirection != null)
{
// 计算当前水平段方向
var currentDx = endPoint.Position.X - startPoint.Position.X;
var currentDy = endPoint.Position.Y - startPoint.Position.Y;
var currentLength = Math.Sqrt(currentDx * currentDx + currentDy * currentDy);
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)
{
var currentDirection = new Vector3D(currentDx / currentLength, currentDy / currentLength, 0);
// 计算与初始水平段方向的点积
double dotProduct = currentDirection.X * horizontalDirection.X + currentDirection.Y * horizontalDirection.Y;
double dotProduct =
currentDirection.X * horizontalDirection.X +
currentDirection.Y * horizontalDirection.Y +
currentDirection.Z * horizontalDirection.Z;
// 点积接近0表示垂直90度转折
isTurn90Horizontal = Math.Abs(dotProduct) < 0.1;
}
@ -1991,27 +1996,26 @@ namespace NavisworksTransport
{
// 层间垂直过渡:通行空间总高度 = 层间路径高度 + 物体高度
// 顶面中心 = 路径高点,底面中心 = 路径低点向下物体高度
bool isStartHigher = adjustedStartPoint.Z > adjustedEndPoint.Z;
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
bool isStartHigher =
HoistingCoordinateHelper.GetElevation(adjustedStartPoint, adapter) >
HoistingCoordinateHelper.GetElevation(adjustedEndPoint, adapter);
if (isStartHigher)
{
// 起点是高点,终点是低点
// 顶面在起点,底面在终点向下延伸物体高度
adjustedEndPoint = new Point3D(
adjustedEndPoint.X,
adjustedEndPoint.Y,
adjustedEndPoint.Z - _objectHeight
);
adjustedEndPoint = HoistingCoordinateHelper.ExtendVerticalTransitionForObjectSpace(
adjustedEndPoint,
_objectHeight,
isLowerPoint: true,
adapter: adapter);
}
else
{
// 终点是高点,起点是低点
// 顶面在终点,底面在起点向下延伸物体高度
adjustedStartPoint = new Point3D(
adjustedStartPoint.X,
adjustedStartPoint.Y,
adjustedStartPoint.Z - _objectHeight
);
adjustedStartPoint = HoistingCoordinateHelper.ExtendVerticalTransitionForObjectSpace(
adjustedStartPoint,
_objectHeight,
isLowerPoint: true,
adapter: adapter);
}
}
// 起吊/下降段不需要延伸,因为底面对齐地面
@ -2062,6 +2066,16 @@ namespace NavisworksTransport
);
}
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();

View File

@ -778,46 +778,51 @@ namespace NavisworksTransport.Core
try
{
var operations = new List<UIUpdateOperation>();
// 收集当前队列中的所有操作
while (_updateQueue.TryDequeue(out var operation))
while (true)
{
operations.Add(operation);
}
var operations = new List<UIUpdateOperation>();
if (operations.Count == 0)
{
return;
}
// 收集当前队列中的所有操作
while (_updateQueue.TryDequeue(out var operation))
{
operations.Add(operation);
}
// 按优先级排序
operations = operations.OrderByDescending(op => op.Priority).ToList();
if (operations.Count == 0)
{
break;
}
LogManager.Info($"开始处理{operations.Count}个排队的UI更新操作强制同步: {forcedSync}");
// 按优先级排序
operations = operations.OrderByDescending(op => op.Priority).ToList();
// 批量执行操作
var actions = operations.Select(op => op.Operation).ToArray();
if (forcedSync)
{
// 使用同步版本的批量更新
LogManager.Info("使用同步方式处理UI更新队列");
ExecuteBatchUIUpdateSync(actions);
LogManager.Info($"同步处理{operations.Count}个UI更新操作完成");
}
else
{
// 使用异步版本的批量更新
LogManager.Info("使用异步方式处理UI更新队列");
await ExecuteBatchUIUpdate(actions);
LogManager.Info($"异步处理{operations.Count}个UI更新操作完成");
}
// 处理完成后,如果队列为空,停止保底定时器
if (_updateQueue.IsEmpty)
{
StopIdleProcessor();
LogManager.Info($"开始处理{operations.Count}个排队的UI更新操作强制同步: {forcedSync}");
// 批量执行操作
var actions = operations.Select(op => op.Operation).ToArray();
if (forcedSync)
{
// 使用同步版本的批量更新
LogManager.Info("使用同步方式处理UI更新队列");
ExecuteBatchUIUpdateSync(actions);
LogManager.Info($"同步处理{operations.Count}个UI更新操作完成");
}
else
{
// 使用异步版本的批量更新
LogManager.Info("使用异步方式处理UI更新队列");
await ExecuteBatchUIUpdate(actions);
LogManager.Info($"异步处理{operations.Count}个UI更新操作完成");
}
if (_updateQueue.IsEmpty)
{
StopIdleProcessor();
break;
}
LogManager.Info($"检测到执行过程中新增 {_updateQueue.Count} 个UI更新操作继续处理后续批次");
}
}
catch (Exception ex)
@ -952,4 +957,4 @@ namespace NavisworksTransport.Core
}
#endregion
}
}

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.PathPlanning
{
@ -12,12 +13,24 @@ namespace NavisworksTransport.PathPlanning
/// </summary>
public class AerialPathGenerator
{
private readonly HostCoordinateAdapter _adapter;
/// <summary>
/// 方向判断容差(米)
/// 当两个方向的距离差小于此值时,默认选择纵向
/// </summary>
private const double DIRECTION_TOLERANCE = 0.001;
public AerialPathGenerator()
: this(HostCoordinateAdapter.CreateFromManager())
{
}
public AerialPathGenerator(HostCoordinateAdapter adapter)
{
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
}
/// <summary>
/// 生成吊装路径(起点吊起 → 平移 → 终点吊下)
/// 保留原有方法以保持向后兼容
@ -49,10 +62,7 @@ namespace NavisworksTransport.PathPlanning
pathPoints.Add(startPointPath);
// 点2提升点起点正上方达到提升高度
var liftPoint = new Point3D(
startPoint.X,
startPoint.Y,
startPoint.Z + liftHeightModelUnits);
var liftPoint = HoistingCoordinateHelper.OffsetAlongUp(startPoint, liftHeightModelUnits, _adapter);
var liftPointPath = new PathPoint(
liftPoint,
"提升点",
@ -61,10 +71,7 @@ namespace NavisworksTransport.PathPlanning
pathPoints.Add(liftPointPath);
// 点3平移终点终点正上方保持提升高度
var moveEndPoint = new Point3D(
endPoint.X,
endPoint.Y,
liftPoint.Z); // 使用提升点的Z坐标保持提升高度一致
var moveEndPoint = HoistingCoordinateHelper.ProjectToAerialElevation(endPoint, liftPoint, _adapter);
var moveEndPointPath = new PathPoint(
moveEndPoint,
"平移终点",
@ -109,10 +116,10 @@ namespace NavisworksTransport.PathPlanning
double deltaY = Math.Abs(clickedGroundPoint.Y - previousPoint.Y);
// 使用目标高度作为Z坐标XY使用地面点击位置
Point3D newPoint = new Point3D(
clickedGroundPoint.X,
clickedGroundPoint.Y,
targetHeightModelUnits); // 使用传入的目标高度
Point3D newPoint = HoistingCoordinateHelper.SetElevation(
clickedGroundPoint,
targetHeightModelUnits,
_adapter);
// 判断移动方向(仅用于记录,不影响位置)
HoistingPointDirection direction;
@ -165,6 +172,7 @@ namespace NavisworksTransport.PathPlanning
var pathPoints = new List<PathPoint>();
double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMeters);
double liftElevation = HoistingCoordinateHelper.GetElevation(startPoint, _adapter) + liftHeightModelUnits;
// 点1起吊点地面起点
var startPathPoint = new PathPoint(
@ -175,10 +183,7 @@ namespace NavisworksTransport.PathPlanning
pathPoints.Add(startPathPoint);
// 点2提升点起点正上方吊装高度
var liftPoint = new Point3D(
startPoint.X,
startPoint.Y,
startPoint.Z + liftHeightModelUnits);
var liftPoint = HoistingCoordinateHelper.OffsetAlongUp(startPoint, liftHeightModelUnits, _adapter);
var liftPathPoint = new PathPoint(
liftPoint,
"提升点",
@ -194,7 +199,7 @@ namespace NavisworksTransport.PathPlanning
var newPoint = GenerateSmartHoistingPoint(
previousPoint,
groundIntermediatePoints[i],
liftHeightModelUnits, // 使用模型单位的高度
liftElevation,
i + 1);
pathPoints.Add(newPoint);
@ -258,13 +263,10 @@ namespace NavisworksTransport.PathPlanning
{
var level = levels[i];
double levelHeightModelUnits = UnitsConverter.ConvertFromMeters(level.HeightInMeters);
double currentHeight = startPoint.Z + levelHeightModelUnits;
double currentHeight = HoistingCoordinateHelper.GetElevation(startPoint, _adapter) + levelHeightModelUnits;
// 垂直移动点(上升或下降到该层级高度)
var verticalPoint = new Point3D(
currentPoint.X,
currentPoint.Y,
currentHeight);
var verticalPoint = HoistingCoordinateHelper.SetElevation(currentPoint, currentHeight, _adapter);
string verticalPointName = level.IsRise ? $"提升点{i + 1}" : $"下降点{i + 1}";
var verticalPathPoint = new PathPoint(
@ -275,10 +277,7 @@ namespace NavisworksTransport.PathPlanning
pathPoints.Add(verticalPathPoint);
// 水平移动点(移动到该层级的目标位置,保持高度)
var horizontalPoint = new Point3D(
level.HorizontalTarget.X,
level.HorizontalTarget.Y,
currentHeight);
var horizontalPoint = HoistingCoordinateHelper.SetElevation(level.HorizontalTarget, currentHeight, _adapter);
string horizontalPointName = $"平移点{i + 1}";
var horizontalPathPoint = new PathPoint(
@ -293,10 +292,7 @@ namespace NavisworksTransport.PathPlanning
}
// 最后:垂直下降到地面终点
var descendPoint = new Point3D(
currentPoint.X,
currentPoint.Y,
endPoint.Z);
var descendPoint = HoistingCoordinateHelper.ProjectToAerialElevation(endPoint, currentPoint, _adapter);
var descendPathPoint = new PathPoint(
descendPoint,
"最终下降点",
@ -315,4 +311,4 @@ namespace NavisworksTransport.PathPlanning
return pathPoints;
}
}
}
}

View File

@ -2872,6 +2872,79 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return points;
}
private bool SyncPathViewModelFromCoreRoute(PathRouteViewModel pathViewModel, PathRoute coreRoute, bool preserveSelection = true)
{
if (pathViewModel == null || coreRoute == null)
{
return false;
}
bool needsUpdate = !ReferenceEquals(pathViewModel.Route, coreRoute) ||
pathViewModel.Points.Count != coreRoute.Points.Count;
if (!needsUpdate && pathViewModel.Points.Count == coreRoute.Points.Count)
{
for (int i = 0; i < pathViewModel.Points.Count; i++)
{
var uiPoint = pathViewModel.Points[i];
var corePoint = coreRoute.Points[i];
if (uiPoint.Id != corePoint.Id ||
uiPoint.Name != corePoint.Name ||
uiPoint.Type != corePoint.Type)
{
needsUpdate = true;
break;
}
const double tolerance = 1e-6;
if (Math.Abs(uiPoint.X - corePoint.Position.X) > tolerance ||
Math.Abs(uiPoint.Y - corePoint.Position.Y) > tolerance ||
Math.Abs(uiPoint.Z - corePoint.Position.Z) > tolerance)
{
needsUpdate = true;
break;
}
}
}
pathViewModel.Route = coreRoute;
pathViewModel.SetTimeInfo(coreRoute.CreatedTime, coreRoute.LastModified);
if (!needsUpdate)
{
pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.TotalLength));
pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.LastModifiedTime));
return false;
}
string selectedPointId = preserveSelection ? SelectedPathPoint?.Id : null;
bool hadSelection = !string.IsNullOrEmpty(selectedPointId);
PathPointViewModel newSelectedPoint = null;
pathViewModel.Points.Clear();
foreach (var pointViewModel in CreatePathPointViewModelsFromCoreRoute(coreRoute))
{
pathViewModel.Points.Add(pointViewModel);
if (hadSelection && pointViewModel.Id == selectedPointId)
{
newSelectedPoint = pointViewModel;
}
}
pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.PointCount));
pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.TotalLength));
pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.SummaryInfo));
pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.LastModifiedTime));
if (hadSelection)
{
SelectedPathPoint = newSelectedPoint;
}
return true;
}
/// <summary>
/// 更新吊装路径的关联点UI层
/// </summary>
@ -4825,90 +4898,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
var pathViewModel = PathRoutes.FirstOrDefault(p => p.Name == e.Route.Name);
if (pathViewModel != null)
{
// 检查是否需要更新:数量变化或名称/类型/坐标变化
bool needsUpdate = pathViewModel.Points.Count != e.Route.Points.Count;
if (!needsUpdate && pathViewModel.Points.Count == e.Route.Points.Count)
{
// 数量相同时,检查名称、类型和坐标是否有变化
for (int i = 0; i < pathViewModel.Points.Count; i++)
{
var uiPoint = pathViewModel.Points[i];
var corePoint = e.Route.Points[i];
// 检查名称和类型变化
if (uiPoint.Name != corePoint.Name || uiPoint.Type != corePoint.Type)
{
needsUpdate = true;
LogManager.Info($"检测到路径点属性变化: {uiPoint.Name}({uiPoint.Type}) -> {corePoint.Name}({corePoint.Type})");
break;
}
// 检查坐标变化(使用小数位精度比较)
const double tolerance = 1e-6; // 1微米精度
if (Math.Abs(uiPoint.X - corePoint.Position.X) > tolerance ||
Math.Abs(uiPoint.Y - corePoint.Position.Y) > tolerance ||
Math.Abs(uiPoint.Z - corePoint.Position.Z) > tolerance)
{
needsUpdate = true;
LogManager.Info($"检测到路径点坐标变化: {uiPoint.Name} " +
$"({uiPoint.X:F3}, {uiPoint.Y:F3}, {uiPoint.Z:F3}) -> " +
$"({corePoint.Position.X:F3}, {corePoint.Position.Y:F3}, {corePoint.Position.Z:F3})");
break;
}
}
}
if (!needsUpdate)
bool updated = SyncPathViewModelFromCoreRoute(pathViewModel, e.Route, preserveSelection: true);
if (!updated)
{
LogManager.Info($"路径点数据无变化({pathViewModel.Points.Count}),跳过更新");
return;
}
// 保存当前选中点的Id以便在重新创建后恢复选中状态
string selectedPointId = SelectedPathPoint?.Id;
bool hadSelection = !string.IsNullOrEmpty(selectedPointId);
// 同步路径点数据
pathViewModel.Points.Clear();
PathPointViewModel newSelectedPoint = null;
for (int i = 0; i < e.Route.Points.Count; i++)
{
var point = e.Route.Points[i];
var pointViewModel = new PathPointViewModel
{
Id = point.Id,
Name = point.Name,
X = point.Position.X,
Y = point.Position.Y,
Z = point.Position.Z,
Type = point.Type,
Index = i
};
pathViewModel.Points.Add(pointViewModel);
LogManager.Debug($"[路径点列表更新] 索引{i}: {point.Name}, Id={point.Id}, 位置=({point.Position.X:F2}, {point.Position.Y:F2}, {point.Position.Z:F2})");
// 如果是之前选中的点,保存引用
if (hadSelection && point.Id == selectedPointId)
{
newSelectedPoint = pointViewModel;
}
}
LogManager.Info($"已更新路径点列表,当前点数: {pathViewModel.Points.Count}");
// 恢复选中状态使用新的ViewModel实例
if (hadSelection && newSelectedPoint != null)
{
SelectedPathPoint = newSelectedPoint;
LogManager.Info($"[路径点列表更新] 恢复选中状态: {newSelectedPoint.Name}, 索引: {newSelectedPoint.Index}");
}
else if (hadSelection && newSelectedPoint == null)
{
// 之前选中的点已被删除
SelectedPathPoint = null;
LogManager.Info("[路径点列表更新] 之前选中的点已被删除,清空选中状态");
}
// 更新真实路径可视化
UpdatePathVisualization();
}
@ -4941,6 +4939,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
var pathViewModel = PathRoutes.FirstOrDefault(p => p.Name == e.NewRoute.Name);
if (pathViewModel != null)
{
bool synced = SyncPathViewModelFromCoreRoute(pathViewModel, e.NewRoute, preserveSelection: true);
if (synced)
{
LogManager.Info($"当前路径切换时已同步路径点列表: {e.NewRoute.Name}, 点数: {pathViewModel.Points.Count}");
}
// 只在真的不同时才设置,避免重复触发
if (SelectedPathRoute != pathViewModel)
{
@ -5121,8 +5125,26 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
else if (e.GenerationMethod == RouteGenerationMethod.Manual)
{
// 手工路径生成(如果需要特殊处理)
LogManager.Info($"手工路径生成完成: {e.Route.Name}");
LogManager.Info($"手工路径生成完成: {e.Route.Name},开始刷新路径列表");
var existingPath = PathRoutes.FirstOrDefault(p => p.Name == e.Route.Name);
if (existingPath == null)
{
existingPath = new PathRouteViewModel(isFromDatabase: true)
{
Route = e.Route,
IsActive = true
};
PathRoutes.Add(existingPath);
LogManager.Info($"手工路径生成后补建UI路径壳: {e.Route.Name}");
}
SyncPathViewModelFromCoreRoute(existingPath, e.Route, preserveSelection: true);
SelectedPathRoute = existingPath;
LogManager.Info($"手工路径已同步并重新选中: {e.Route.Name},点数: {existingPath.Points.Count}");
OnPropertyChanged(nameof(PathRoutes));
OnPropertyChanged(nameof(CanExecuteStartEdit));
OnPropertyChanged(nameof(CanExecuteEndEdit));
}
else if (e.GenerationMethod == RouteGenerationMethod.DatabaseLoad)
{

View File

@ -0,0 +1,205 @@
using System;
using System.Numerics;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// 吊装路径在宿主坐标与 Canonical Space 之间的坐标语义辅助。
/// 统一表达“沿宿主 up 的高程”和“宿主水平面投影”,避免把世界 Z 误当成向上轴。
/// </summary>
public static class HoistingCoordinateHelper
{
public static double GetElevation(Point3D hostPoint, HostCoordinateAdapter adapter)
{
if (hostPoint == null)
{
throw new ArgumentNullException(nameof(hostPoint));
}
return GetElevation(ToVector3(hostPoint), adapter);
}
public static double GetElevation(Vector3 hostPoint, HostCoordinateAdapter adapter)
{
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
return adapter.ToCanonicalPoint3(hostPoint).Z;
}
public static Point3D SetElevation(Point3D hostPoint, double elevation, HostCoordinateAdapter adapter)
{
if (hostPoint == null)
{
throw new ArgumentNullException(nameof(hostPoint));
}
return ToPoint3D(SetElevation3(ToVector3(hostPoint), elevation, adapter));
}
public static Vector3 SetElevation3(Vector3 hostPoint, double elevation, HostCoordinateAdapter adapter)
{
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
Vector3 canonicalPoint = adapter.ToCanonicalPoint3(hostPoint);
Vector3 canonicalResult = new Vector3(canonicalPoint.X, canonicalPoint.Y, (float)elevation);
return adapter.FromCanonicalPoint3(canonicalResult);
}
public static Point3D OffsetAlongUp(Point3D hostPoint, double offset, HostCoordinateAdapter adapter)
{
return ToPoint3D(OffsetAlongUp3(ToVector3(hostPoint), offset, adapter));
}
public static Vector3 OffsetAlongUp3(Vector3 hostPoint, double offset, HostCoordinateAdapter adapter)
{
return SetElevation3(hostPoint, GetElevation(hostPoint, adapter) + offset, adapter);
}
public static Point3D ProjectToAerialElevation(Point3D groundPoint, Point3D aerialReferencePoint, HostCoordinateAdapter adapter)
{
return ToPoint3D(ProjectToAerialElevation3(ToVector3(groundPoint), ToVector3(aerialReferencePoint), adapter));
}
public static Vector3 ProjectToAerialElevation3(Vector3 groundPoint, Vector3 aerialReferencePoint, HostCoordinateAdapter adapter)
{
return SetElevation3(groundPoint, GetElevation(aerialReferencePoint, adapter), adapter);
}
public static double GetRelativeHeight(Point3D groundPoint, Point3D aerialPoint, HostCoordinateAdapter adapter)
{
return GetRelativeHeight3(ToVector3(groundPoint), ToVector3(aerialPoint), adapter);
}
public static double GetRelativeHeight3(Vector3 groundPoint, Vector3 aerialPoint, HostCoordinateAdapter adapter)
{
return GetElevation(aerialPoint, adapter) - GetElevation(groundPoint, adapter);
}
public static Point3D CreateHorizontalTurnPoint(
Point3D currentPoint,
Point3D nextPoint,
bool keepCurrentFirstHorizontalAxis,
HostCoordinateAdapter adapter)
{
if (currentPoint == null)
{
throw new ArgumentNullException(nameof(currentPoint));
}
if (nextPoint == null)
{
throw new ArgumentNullException(nameof(nextPoint));
}
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
return ToPoint3D(CreateHorizontalTurnPoint3(
ToVector3(currentPoint),
ToVector3(nextPoint),
keepCurrentFirstHorizontalAxis,
adapter));
}
public static Vector3 CreateHorizontalTurnPoint3(
Vector3 currentPoint,
Vector3 nextPoint,
bool keepCurrentFirstHorizontalAxis,
HostCoordinateAdapter adapter)
{
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
Vector3 canonicalCurrent = adapter.ToCanonicalPoint3(currentPoint);
Vector3 canonicalNext = adapter.ToCanonicalPoint3(nextPoint);
Vector3 canonicalIntermediate = keepCurrentFirstHorizontalAxis
? new Vector3(canonicalCurrent.X, canonicalNext.Y, canonicalCurrent.Z)
: new Vector3(canonicalNext.X, canonicalCurrent.Y, canonicalCurrent.Z);
return adapter.FromCanonicalPoint3(canonicalIntermediate);
}
public static Vector3 GetHorizontalDirection3(Vector3 fromPoint, Vector3 toPoint, HostCoordinateAdapter adapter)
{
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
Vector3 canonicalFrom = adapter.ToCanonicalPoint3(fromPoint);
Vector3 canonicalTo = adapter.ToCanonicalPoint3(toPoint);
Vector3 canonicalHorizontal = new Vector3(
canonicalTo.X - canonicalFrom.X,
canonicalTo.Y - canonicalFrom.Y,
0.0f);
float horizontalLength = canonicalHorizontal.Length();
if (horizontalLength < 1e-6f)
{
return Vector3.Zero;
}
Vector3 canonicalDirection = canonicalHorizontal / horizontalLength;
return adapter.FromCanonicalVector3(canonicalDirection);
}
public static Point3D ExtendVerticalTransitionForObjectSpace(
Point3D hostPoint,
double objectHeight,
bool isLowerPoint,
HostCoordinateAdapter adapter)
{
if (hostPoint == null)
{
throw new ArgumentNullException(nameof(hostPoint));
}
return ToPoint3D(ExtendVerticalTransitionForObjectSpace3(
ToVector3(hostPoint),
objectHeight,
isLowerPoint,
adapter));
}
public static Vector3 ExtendVerticalTransitionForObjectSpace3(
Vector3 hostPoint,
double objectHeight,
bool isLowerPoint,
HostCoordinateAdapter adapter)
{
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
if (!isLowerPoint || Math.Abs(objectHeight) < 1e-9)
{
return hostPoint;
}
return OffsetAlongUp3(hostPoint, -objectHeight, adapter);
}
private static Vector3 ToVector3(Point3D point)
{
return new Vector3((float)point.X, (float)point.Y, (float)point.Z);
}
private static Point3D ToPoint3D(Vector3 point)
{
return new Point3D(point.X, point.Y, point.Z);
}
}
}