Align terminal rail normals and object space rendering
This commit is contained in:
parent
e9128282b5
commit
1cf4fe5967
@ -56,6 +56,7 @@
|
||||
<Compile Include="UnitTests\CoordinateSystem\HostCoordinateAdapterTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\CanonicalRailPoseBuilderTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\CanonicalPlanarPoseBuilderTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\ObjectSpaceOrientationHelperTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\CanonicalRailOffsetResolverTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\CanonicalTrackedPositionResolverTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\HoistingCoordinateHelperTests.cs" />
|
||||
|
||||
@ -338,6 +338,7 @@
|
||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailOffsetResolver.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailPoseBuilder.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalTrackedPositionResolver.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\ObjectSpaceOrientationHelper.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\HostCoordinateAdapter.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\HoistingCoordinateHelper.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\LocalAxisDirection.cs" />
|
||||
|
||||
@ -27,6 +27,61 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
Assert.AreEqual(0f, Vector3.Dot(result.PlaneNormal, result.PlaneSpanDirection), 1e-4f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BuildFromTwoPoints_ShouldCreatePlaneParallelToOpticalAxisAndContainBothPoints()
|
||||
{
|
||||
Vector3 axisBase = new Vector3(0f, 0f, 0f);
|
||||
Vector3 axisDirection = Vector3.UnitX;
|
||||
Vector3 pickPoint1 = new Vector3(2f, 3f, 4f);
|
||||
Vector3 pickPoint2 = new Vector3(6f, 3f, 6f);
|
||||
|
||||
AssemblyInstallationReferenceResult result =
|
||||
AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint1, pickPoint2);
|
||||
|
||||
Assert.AreEqual(3f, result.OffsetDistance, 1e-4f);
|
||||
Assert.AreEqual(4f, result.AnchorPoint.X, 1e-4f);
|
||||
Assert.AreEqual(3f, result.AnchorPoint.Y, 1e-4f);
|
||||
Assert.AreEqual(0f, result.AnchorPoint.Z, 1e-4f);
|
||||
Assert.AreEqual(0f, Vector3.Dot(result.PlaneNormal, result.OpticalAxisDirection), 1e-4f);
|
||||
Assert.AreEqual(0f, Vector3.Dot(result.PlaneSpanDirection, result.OpticalAxisDirection), 1e-4f);
|
||||
Assert.AreEqual(0f, Vector3.Dot(result.PlaneNormal, result.PlaneSpanDirection), 1e-4f);
|
||||
Assert.AreEqual(0f, Vector3.Dot(pickPoint1 - result.AnchorPoint, result.PlaneNormal), 1e-4f);
|
||||
Assert.AreEqual(0f, Vector3.Dot(pickPoint2 - result.AnchorPoint, result.PlaneNormal), 1e-4f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BuildFromTwoPoints_ShouldUseOpticalAxisProjectionAsInstallationCenterLine()
|
||||
{
|
||||
Vector3 axisBase = new Vector3(0f, 0f, 0f);
|
||||
Vector3 axisDirection = Vector3.UnitX;
|
||||
Vector3 pickPoint1 = new Vector3(2f, 3f, 4f);
|
||||
Vector3 pickPoint2 = new Vector3(6f, 3f, 6f);
|
||||
|
||||
AssemblyInstallationReferenceResult result =
|
||||
AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint1, pickPoint2);
|
||||
|
||||
Assert.AreEqual(0f, result.InstallLineBasePoint.X, 1e-4f);
|
||||
Assert.AreEqual(3f, result.InstallLineBasePoint.Y, 1e-4f);
|
||||
Assert.AreEqual(0f, result.InstallLineBasePoint.Z, 1e-4f);
|
||||
Assert.AreEqual(4f, result.AnchorPoint.X, 1e-4f);
|
||||
Assert.AreEqual(3f, result.AnchorPoint.Y, 1e-4f);
|
||||
Assert.AreEqual(0f, result.AnchorPoint.Z, 1e-4f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BuildFromTwoPoints_WhenPlanePointsAreNearlyAxisAligned_ShouldThrow()
|
||||
{
|
||||
Vector3 axisBase = new Vector3(0f, 0f, 0f);
|
||||
Vector3 axisDirection = Vector3.UnitX;
|
||||
Vector3 pickPoint1 = new Vector3(2f, 3f, 4f);
|
||||
Vector3 pickPoint2 = new Vector3(6f, 3.00001f, 4.00001f);
|
||||
|
||||
InvalidOperationException ex = Assert.ThrowsException<InvalidOperationException>(
|
||||
() => AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint1, pickPoint2));
|
||||
|
||||
StringAssert.Contains(ex.Message, "无法确定安装参考面");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Build_WhenPickPointTooCloseToOpticalAxis_ShouldThrow()
|
||||
{
|
||||
|
||||
@ -137,6 +137,23 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
AssertVector(mappedUp, 0, 0, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreferredNormal_ShouldOverrideCanonicalUpForRailFrame()
|
||||
{
|
||||
bool ok = CanonicalRailPoseBuilder.TryCreateLocalFrame(
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(1, 0, 0),
|
||||
new Vector3(2, 0, 0),
|
||||
Vector3.UnitZ,
|
||||
Vector3.UnitY,
|
||||
out RailLocalFrame frame);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
AssertVector(frame.Forward, 1, 0, 0);
|
||||
AssertVector(frame.Normal, 0, 1, 0);
|
||||
AssertVector(frame.Lateral, 0, 0, -1);
|
||||
}
|
||||
|
||||
private static void AssertColumn(Matrix4x4 matrix, int column, double x, double y, double z)
|
||||
{
|
||||
switch (column)
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
using System.Numerics;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.Utils.CoordinateSystem;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
{
|
||||
[TestClass]
|
||||
public class ObjectSpaceOrientationHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void CalculateAxes_ShouldHonorProvidedUpReference()
|
||||
{
|
||||
var segmentDirection = new Vector3(10f, 0f, 0f);
|
||||
var upReference = new Vector3(0f, 1f, 0f);
|
||||
|
||||
var (_, up) = ObjectSpaceOrientationHelper.CalculateAxes(segmentDirection, upReference);
|
||||
|
||||
Assert.AreEqual(0.0, up.X, 1e-6);
|
||||
Assert.AreEqual(1.0, System.Math.Abs(up.Y), 1e-6);
|
||||
Assert.AreEqual(0.0, up.Z, 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CalculateAxes_ForVerticalSegment_ShouldUseHorizontalDirectionWithProvidedUpReference()
|
||||
{
|
||||
var segmentDirection = new Vector3(0f, 10f, 0f);
|
||||
var upReference = new Vector3(0f, 1f, 0f);
|
||||
var horizontalDirection = new Vector3(1f, 0f, 0f);
|
||||
|
||||
var (right, up) = ObjectSpaceOrientationHelper.CalculateAxes(segmentDirection, upReference, horizontalDirection);
|
||||
|
||||
Assert.AreEqual(0.0, right.X, 1e-6);
|
||||
Assert.AreEqual(0.0, right.Y, 1e-6);
|
||||
Assert.AreEqual(1.0, right.Z, 1e-6);
|
||||
Assert.AreEqual(1.0, up.X, 1e-6);
|
||||
Assert.AreEqual(0.0, up.Y, 1e-6);
|
||||
Assert.AreEqual(0.0, up.Z, 1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -671,6 +671,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
LogManager.Debug($"[移动到起点] Rail路径调整: 参考点=({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2}), 物体中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), 物体高度={objectHeight:F2}, 安装={_route.RailMountMode}, 对接={(PathRoute.IsTopPayloadAnchorForMountMode(_route.RailMountMode) ? "顶面对接" : "底面对接")}");
|
||||
|
||||
if (RailPathPoseHelper.TryCreateRailRotation(
|
||||
_route,
|
||||
previousPoint,
|
||||
_pathPoints[0],
|
||||
nextPoint,
|
||||
@ -1034,6 +1035,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
|
||||
if (_route.PathType == PathType.Rail &&
|
||||
RailPathPoseHelper.TryCreateRailRotation(
|
||||
_route,
|
||||
previousFramePoint,
|
||||
framePosition,
|
||||
nextFramePoint,
|
||||
|
||||
@ -737,6 +737,12 @@ namespace NavisworksTransport
|
||||
/// </summary>
|
||||
public RailPathDefinitionMode RailPathDefinitionMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rail 路径优先法向(宿主坐标)。
|
||||
/// 仅在存在显式安装面/业务法向时使用;为空时退回当前默认求解。
|
||||
/// </summary>
|
||||
public Point3D RailPreferredNormal { get; set; }
|
||||
|
||||
// 数据库分析相关属性
|
||||
/// <summary>
|
||||
/// 碰撞数量(从数据库加载)
|
||||
@ -777,6 +783,7 @@ namespace NavisworksTransport
|
||||
IsCurved = false;
|
||||
RailMountMode = RailMountMode.UnderRail;
|
||||
RailPathDefinitionMode = RailPathDefinitionMode.LegacySuspensionPoint;
|
||||
RailPreferredNormal = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -297,6 +297,11 @@ namespace NavisworksTransport
|
||||
/// </summary>
|
||||
public Vector3D HorizontalDirection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通行空间的优先 up 方向(例如 Rail 安装面法向)
|
||||
/// </summary>
|
||||
public Vector3D UpDirection { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"LineMarker[{FromIndex}->{ToIndex}, 类型={SegmentType}, 起点=({StartPoint.X:F2},{StartPoint.Y:F2},{StartPoint.Z:F2}), 终点=({EndPoint.X:F2},{EndPoint.Y:F2},{EndPoint.Z:F2})]";
|
||||
@ -1658,7 +1663,8 @@ namespace NavisworksTransport
|
||||
var endPoint = edge.SampledPoints.Last();
|
||||
|
||||
// 计算长方体的轴向量(right和up向量)
|
||||
var (right, up) = CalculateCuboidAxes(startPoint, endPoint);
|
||||
var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute);
|
||||
var (right, up) = CalculateCuboidAxes(startPoint, endPoint, preferredUp);
|
||||
|
||||
// 根据路径类型调整起点和终点位置
|
||||
// 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移
|
||||
@ -1710,7 +1716,8 @@ namespace NavisworksTransport
|
||||
SampledPoints = edge.SampledPoints,
|
||||
Height = height, // 根据模式设置高度
|
||||
Width = width, // 根据模式设置宽度
|
||||
Opacity = lineOpacity // 根据模式设置透明度
|
||||
Opacity = lineOpacity, // 根据模式设置透明度
|
||||
UpDirection = preferredUp
|
||||
};
|
||||
visualization.PathLineMarkers.Add(lineMarker);
|
||||
}
|
||||
@ -1736,7 +1743,8 @@ namespace NavisworksTransport
|
||||
var endPoint = edge.Trajectory.Te;
|
||||
|
||||
// 计算长方体的轴向量(right和up向量)
|
||||
var (right, up) = CalculateCuboidAxes(startPoint, endPoint);
|
||||
var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute);
|
||||
var (right, up) = CalculateCuboidAxes(startPoint, endPoint, preferredUp);
|
||||
|
||||
// 根据路径类型调整起点和终点位置
|
||||
// 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移
|
||||
@ -1818,7 +1826,8 @@ namespace NavisworksTransport
|
||||
SampledPoints = adjustedSampledPoints,
|
||||
Height = height, // 根据模式设置高度
|
||||
Width = width, // 根据模式设置宽度
|
||||
Opacity = arcLineOpacity // 根据模式设置透明度
|
||||
Opacity = arcLineOpacity, // 根据模式设置透明度
|
||||
UpDirection = preferredUp
|
||||
};
|
||||
visualization.PathLineMarkers.Add(arcMarker);
|
||||
|
||||
@ -1838,73 +1847,13 @@ namespace NavisworksTransport
|
||||
/// <param name="endPoint">路径段终点</param>
|
||||
/// <param name="horizontalDirection">水平段方向向量(用于垂直路径渲染时确定通行空间方向)</param>
|
||||
/// <returns>元组(right向量,up向量)</returns>
|
||||
private (Vector3D right, Vector3D up) CalculateCuboidAxes(Point3D startPoint, Point3D endPoint, Vector3D horizontalDirection = null)
|
||||
private (Vector3D right, Vector3D up) CalculateCuboidAxes(Point3D startPoint, Point3D endPoint, Vector3D upReference = null, Vector3D horizontalDirection = null)
|
||||
{
|
||||
// 计算路径段的方向向量
|
||||
var segmentDirection = new Vector3D(
|
||||
endPoint.X - startPoint.X,
|
||||
endPoint.Y - startPoint.Y,
|
||||
endPoint.Z - startPoint.Z
|
||||
);
|
||||
var segmentLength = Math.Sqrt(
|
||||
segmentDirection.X * segmentDirection.X +
|
||||
segmentDirection.Y * segmentDirection.Y +
|
||||
segmentDirection.Z * segmentDirection.Z
|
||||
);
|
||||
|
||||
// 归一化方向向量
|
||||
var normalizedDirection = new Vector3D(
|
||||
segmentDirection.X / segmentLength,
|
||||
segmentDirection.Y / segmentLength,
|
||||
segmentDirection.Z / segmentLength
|
||||
);
|
||||
|
||||
// 使用宿主 up 方向构造水平 right 向量,避免把世界 Z 写死到 Y-up 项目里。
|
||||
var hostUp = GetHostUpVector();
|
||||
var right = Cross(normalizedDirection, hostUp);
|
||||
|
||||
// 如果方向向量接近宿主 up(垂直段),则使用水平段方向重新构造 right。
|
||||
double parallelToUp = Math.Abs(
|
||||
normalizedDirection.X * hostUp.X +
|
||||
normalizedDirection.Y * hostUp.Y +
|
||||
normalizedDirection.Z * hostUp.Z);
|
||||
if (parallelToUp > 0.9)
|
||||
{
|
||||
if (horizontalDirection != null)
|
||||
{
|
||||
right = Cross(horizontalDirection, hostUp);
|
||||
}
|
||||
else
|
||||
{
|
||||
right = Math.Abs(hostUp.X) < 0.9
|
||||
? Normalize(Cross(hostUp, new Vector3D(1, 0, 0)))
|
||||
: Normalize(Cross(hostUp, new Vector3D(0, 1, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// 归一化right向量
|
||||
var rightLength = Math.Sqrt(right.X * right.X + right.Y * right.Y + right.Z * right.Z);
|
||||
if (rightLength > 0.001)
|
||||
{
|
||||
right = new Vector3D(right.X / rightLength, right.Y / rightLength, right.Z / rightLength);
|
||||
}
|
||||
|
||||
// 计算高度向量(垂直于方向向量和宽度向量)- 这是长方体的轴线方向
|
||||
// up = direction × right
|
||||
var up = new Vector3D(
|
||||
normalizedDirection.Y * right.Z - normalizedDirection.Z * right.Y,
|
||||
normalizedDirection.Z * right.X - normalizedDirection.X * right.Z,
|
||||
normalizedDirection.X * right.Y - normalizedDirection.Y * right.X
|
||||
);
|
||||
|
||||
// 归一化up向量
|
||||
var upLength = Math.Sqrt(up.X * up.X + up.Y * up.Y + up.Z * up.Z);
|
||||
if (upLength > 0.001)
|
||||
{
|
||||
up = new Vector3D(up.X / upLength, up.Y / upLength, up.Z / upLength);
|
||||
}
|
||||
|
||||
return (right, up);
|
||||
return ObjectSpaceOrientationHelper.CalculateAxes(
|
||||
startPoint,
|
||||
endPoint,
|
||||
upReference ?? GetHostUpVector(),
|
||||
horizontalDirection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -2009,7 +1958,8 @@ namespace NavisworksTransport
|
||||
CalculatePassageSpaceParameters(visualization, isVerticalSegment, isTurn90Horizontal, isFirstOrLastSegment);
|
||||
|
||||
// 计算长方体的轴向量(right和up向量)
|
||||
var (right, up) = CalculateCuboidAxes(startPoint.Position, endPoint.Position, horizontalDirection);
|
||||
var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute);
|
||||
var (right, up) = CalculateCuboidAxes(startPoint.Position, endPoint.Position, preferredUp, horizontalDirection);
|
||||
|
||||
// 根据路径类型调整起点和终点位置
|
||||
// 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移,而不是简单地添加到Z坐标上
|
||||
@ -2099,7 +2049,8 @@ namespace NavisworksTransport
|
||||
Height = height,
|
||||
Width = width,
|
||||
Opacity = lineOpacity,
|
||||
HorizontalDirection = horizontalDirection // 设置水平段方向向量
|
||||
HorizontalDirection = horizontalDirection, // 设置水平段方向向量
|
||||
UpDirection = preferredUp
|
||||
};
|
||||
visualization.PathLineMarkers.Add(lineMarker);
|
||||
}
|
||||
@ -2797,7 +2748,7 @@ namespace NavisworksTransport
|
||||
/// <param name="width">宽度</param>
|
||||
/// <param name="height">高度</param>
|
||||
/// <param name="horizontalDirection">水平段方向向量(用于垂直路径渲染时确定通行空间方向)</param>
|
||||
private void RenderCuboidMarker(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, Vector3D horizontalDirection = null)
|
||||
private void RenderCuboidMarker(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, Vector3D horizontalDirection = null, Vector3D upReference = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -2817,47 +2768,11 @@ namespace NavisworksTransport
|
||||
// 归一化方向向量
|
||||
direction = new Vector3D(direction.X / length, direction.Y / length, direction.Z / length);
|
||||
|
||||
var hostUp = GetHostUpVector();
|
||||
var right = Cross(direction, hostUp);
|
||||
|
||||
double parallelToUp = Math.Abs(
|
||||
direction.X * hostUp.X +
|
||||
direction.Y * hostUp.Y +
|
||||
direction.Z * hostUp.Z);
|
||||
if (parallelToUp > 0.9)
|
||||
{
|
||||
if (horizontalDirection != null)
|
||||
{
|
||||
right = Cross(horizontalDirection, hostUp);
|
||||
}
|
||||
else
|
||||
{
|
||||
right = Math.Abs(hostUp.X) < 0.9
|
||||
? Normalize(Cross(hostUp, new Vector3D(1, 0, 0)))
|
||||
: Normalize(Cross(hostUp, new Vector3D(0, 1, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// 归一化right向量
|
||||
var rightLength = Math.Sqrt(right.X * right.X + right.Y * right.Y + right.Z * right.Z);
|
||||
if (rightLength > 0.001)
|
||||
{
|
||||
right = new Vector3D(right.X / rightLength, right.Y / rightLength, right.Z / rightLength);
|
||||
}
|
||||
|
||||
// 计算高度向量(垂直于方向向量和宽度向量)
|
||||
var up = new Vector3D(
|
||||
direction.Y * right.Z - direction.Z * right.Y,
|
||||
direction.Z * right.X - direction.X * right.Z,
|
||||
direction.X * right.Y - direction.Y * right.X
|
||||
);
|
||||
|
||||
// 归一化up向量
|
||||
var upLength = Math.Sqrt(up.X * up.X + up.Y * up.Y + up.Z * up.Z);
|
||||
if (upLength > 0.001)
|
||||
{
|
||||
up = new Vector3D(up.X / upLength, up.Y / upLength, up.Z / upLength);
|
||||
}
|
||||
var (right, up) = ObjectSpaceOrientationHelper.CalculateAxes(
|
||||
startPoint,
|
||||
endPoint,
|
||||
upReference ?? GetHostUpVector(),
|
||||
horizontalDirection);
|
||||
|
||||
// 计算长方体的边界框和向量
|
||||
var halfWidth = width / 2.0;
|
||||
@ -2894,7 +2809,7 @@ namespace NavisworksTransport
|
||||
/// <param name="height">高度</param>
|
||||
/// <param name="length">指定长度(覆盖默认计算值)</param>
|
||||
/// <param name="horizontalDirection">水平段方向向量(用于垂直路径渲染时确定通行空间方向)</param>
|
||||
private void RenderCuboidMarkerWithLength(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, double length, Vector3D horizontalDirection = null)
|
||||
private void RenderCuboidMarkerWithLength(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, double length, Vector3D horizontalDirection = null, Vector3D upReference = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -2912,47 +2827,11 @@ namespace NavisworksTransport
|
||||
|
||||
direction = new Vector3D(direction.X / directionLength, direction.Y / directionLength, direction.Z / directionLength);
|
||||
|
||||
var hostUp = GetHostUpVector();
|
||||
var right = Cross(direction, hostUp);
|
||||
|
||||
double parallelToUp = Math.Abs(
|
||||
direction.X * hostUp.X +
|
||||
direction.Y * hostUp.Y +
|
||||
direction.Z * hostUp.Z);
|
||||
if (parallelToUp > 0.9)
|
||||
{
|
||||
if (horizontalDirection != null)
|
||||
{
|
||||
right = Cross(horizontalDirection, hostUp);
|
||||
}
|
||||
else
|
||||
{
|
||||
right = Math.Abs(hostUp.X) < 0.9
|
||||
? Normalize(Cross(hostUp, new Vector3D(1, 0, 0)))
|
||||
: Normalize(Cross(hostUp, new Vector3D(0, 1, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// 归一化right向量
|
||||
var rightLength = Math.Sqrt(right.X * right.X + right.Y * right.Y + right.Z * right.Z);
|
||||
if (rightLength > 0.001)
|
||||
{
|
||||
right = new Vector3D(right.X / rightLength, right.Y / rightLength, right.Z / rightLength);
|
||||
}
|
||||
|
||||
// 计算高度向量(垂直于方向向量和宽度向量)
|
||||
var up = new Vector3D(
|
||||
direction.Y * right.Z - direction.Z * right.Y,
|
||||
direction.Z * right.X - direction.X * right.Z,
|
||||
direction.X * right.Y - direction.Y * right.X
|
||||
);
|
||||
|
||||
// 归一化up向量
|
||||
var upLength = Math.Sqrt(up.X * up.X + up.Y * up.Y + up.Z * up.Z);
|
||||
if (upLength > 0.001)
|
||||
{
|
||||
up = new Vector3D(up.X / upLength, up.Y / upLength, up.Z / upLength);
|
||||
}
|
||||
var (right, up) = ObjectSpaceOrientationHelper.CalculateAxes(
|
||||
startPoint,
|
||||
endPoint,
|
||||
upReference ?? GetHostUpVector(),
|
||||
horizontalDirection);
|
||||
|
||||
// 计算长方体的边界框和向量
|
||||
var halfWidth = width / 2.0;
|
||||
@ -3009,7 +2888,8 @@ namespace NavisworksTransport
|
||||
lineMarker.EndPoint,
|
||||
width,
|
||||
lineMarker.Height,
|
||||
lineMarker.HorizontalDirection
|
||||
lineMarker.HorizontalDirection,
|
||||
lineMarker.UpDirection
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -3052,11 +2932,25 @@ namespace NavisworksTransport
|
||||
width,
|
||||
lineMarker.Height,
|
||||
_passageAlongPath,
|
||||
lineMarker.HorizontalDirection
|
||||
lineMarker.HorizontalDirection,
|
||||
lineMarker.UpDirection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3D ResolveObjectSpaceUpDirection(PathRoute route)
|
||||
{
|
||||
if (route?.PathType == NavisworksTransport.PathType.Rail && route.RailPreferredNormal != null)
|
||||
{
|
||||
return Normalize(new Vector3D(
|
||||
route.RailPreferredNormal.X,
|
||||
route.RailPreferredNormal.Y,
|
||||
route.RailPreferredNormal.Z));
|
||||
}
|
||||
|
||||
return GetHostUpVector();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算采样点处的切线方向
|
||||
/// </summary>
|
||||
|
||||
@ -154,6 +154,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
private Vector3 _assemblyInstallationPlaneNormal;
|
||||
private Vector3 _assemblyInstallationPlaneSpanDirection;
|
||||
private double _assemblyInstallationOffsetDistanceInMeters;
|
||||
private readonly List<Point3D> _assemblyInstallationSeedPoints = new List<Point3D>();
|
||||
private readonly List<Point3D> _assemblyEndFaceSeedPoints = new List<Point3D>();
|
||||
private const string AssemblyAnchorMarkerPathId = "assembly_anchor_marker";
|
||||
private const string AssemblyCenterGuideLinePathId = "assembly_center_guide_line";
|
||||
@ -1347,6 +1348,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
await SafeExecuteAsync(() =>
|
||||
{
|
||||
ClearNonGridPathVisualizations("[直线装配] 捕获箱体");
|
||||
|
||||
var document = NavisApplication.ActiveDocument;
|
||||
var selectedItem = document?.CurrentSelection?.SelectedItems?.FirstOrDefault();
|
||||
if (selectedItem == null)
|
||||
@ -1379,6 +1382,30 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}, "捕获终点箱体");
|
||||
}
|
||||
|
||||
private void ClearNonGridPathVisualizations(string context)
|
||||
{
|
||||
if (PathPointRenderPlugin.Instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PathPointRenderPlugin.Instance.ClearPathsExcept(
|
||||
"grid_visualization_all",
|
||||
"grid_visualization_channel",
|
||||
"grid_visualization_unknown",
|
||||
"grid_visualization_obstacle",
|
||||
"grid_visualization_door");
|
||||
LogManager.Info($"{context}:已清除现有路径可视化显示(保留网格可视化)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"{context}:清除现有路径可视化失败: {ex.Message}", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteGenerateAssemblyReferenceRodAsync()
|
||||
{
|
||||
await SafeExecuteAsync(() =>
|
||||
@ -1546,7 +1573,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
throw new InvalidOperationException("ToolPlugin 初始化失败,请重试。");
|
||||
}
|
||||
|
||||
UpdateMainStatus("请在终点箱体表面点击安装点,系统将按该点计算安装参考面和终点锚点。");
|
||||
_assemblyInstallationSeedPoints.Clear();
|
||||
UpdateMainStatus("请在终点箱体表面连续点击两个安装面点,系统将按两点和光轴计算安装参考面与安装点。");
|
||||
LogManager.Info("[直线装配] 已进入安装点拾取模式");
|
||||
}, "选择安装点");
|
||||
}
|
||||
@ -1640,7 +1668,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
BuildAndRenderAssemblyInstallationReference(pickResult.Point);
|
||||
_assemblyInstallationSeedPoints.Add(pickResult.Point);
|
||||
RenderAssemblyInstallationPickPoints(_assemblyInstallationSeedPoints);
|
||||
|
||||
int pickedCount = _assemblyInstallationSeedPoints.Count;
|
||||
LogManager.Info($"[直线装配] 已记录安装面点 {pickedCount}: ({pickResult.Point.X:F3}, {pickResult.Point.Y:F3}, {pickResult.Point.Z:F3})");
|
||||
|
||||
if (pickedCount < 2)
|
||||
{
|
||||
UpdateMainStatus("已记录安装面点 1/2,请继续在同一安装面上点击第二个点。");
|
||||
return;
|
||||
}
|
||||
|
||||
BuildAndRenderAssemblyInstallationReference(
|
||||
_assemblyInstallationSeedPoints[0],
|
||||
_assemblyInstallationSeedPoints[1]);
|
||||
CleanupAssemblyInstallationSelection();
|
||||
}, "处理安装点拾取");
|
||||
}
|
||||
@ -1665,6 +1707,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
RailPathDefinitionMode = RailPathDefinitionMode.RailCenterLine
|
||||
};
|
||||
|
||||
if (_hasAssemblyInstallationReference)
|
||||
{
|
||||
route.RailPreferredNormal = new Point3D(
|
||||
_assemblyInstallationPlaneNormal.X,
|
||||
_assemblyInstallationPlaneNormal.Y,
|
||||
_assemblyInstallationPlaneNormal.Z);
|
||||
}
|
||||
|
||||
route.AddPoint(new PathPoint(startPoint, "装配起点", PathPointType.StartPoint));
|
||||
route.AddPoint(new PathPoint(endPoint, "装配终点", PathPointType.EndPoint));
|
||||
|
||||
@ -1706,7 +1756,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
GetAssemblyAnchorText());
|
||||
|
||||
OnPropertyChanged(nameof(PathRoutes));
|
||||
LogManager.Info($"[直线装配] 已生成并完成路径: {route.Name},起点=({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2}),终点=({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2})");
|
||||
LogManager.Info(
|
||||
$"[直线装配] 已生成并完成路径: {route.Name},起点=({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2}),终点=({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2})" +
|
||||
$"{(route.RailPreferredNormal != null ? $",安装法向=({route.RailPreferredNormal.X:F3}, {route.RailPreferredNormal.Y:F3}, {route.RailPreferredNormal.Z:F3})" : string.Empty)}");
|
||||
}
|
||||
|
||||
private void HideAssemblyReferenceVisuals(bool resetStartPointText, string statusMessage, string logMessage)
|
||||
@ -1882,7 +1934,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
: string.Format("{0}点=未选择", anchorText);
|
||||
string installationText = _hasAssemblyInstallationReference
|
||||
? string.Format(
|
||||
"选定安装点=({0:F2}, {1:F2}, {2:F2}),安装点=({3:F2}, {4:F2}, {5:F2}),偏距={6:F3}m",
|
||||
"安装面点中点=({0:F2}, {1:F2}, {2:F2}),安装点=({3:F2}, {4:F2}, {5:F2}),偏距={6:F3}m",
|
||||
_assemblyInstallationPickPoint.X,
|
||||
_assemblyInstallationPickPoint.Y,
|
||||
_assemblyInstallationPickPoint.Z,
|
||||
@ -2214,7 +2266,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
RenderStyleName.AssemblyGuideLine);
|
||||
}
|
||||
|
||||
private void BuildAndRenderAssemblyInstallationReference(Point3D pickPoint)
|
||||
private void BuildAndRenderAssemblyInstallationReference(Point3D firstPickPoint, Point3D secondPickPoint)
|
||||
{
|
||||
Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint();
|
||||
Point3D sphereCenterPoint = new Point3D(
|
||||
@ -2230,10 +2282,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
AssemblyInstallationReferenceResult result = AssemblyInstallationReferenceBuilder.Build(
|
||||
new Vector3((float)opticalAxisReferencePoint.X, (float)opticalAxisReferencePoint.Y, (float)opticalAxisReferencePoint.Z),
|
||||
opticalAxisDirection,
|
||||
new Vector3((float)pickPoint.X, (float)pickPoint.Y, (float)pickPoint.Z));
|
||||
new Vector3((float)firstPickPoint.X, (float)firstPickPoint.Y, (float)firstPickPoint.Z),
|
||||
new Vector3((float)secondPickPoint.X, (float)secondPickPoint.Y, (float)secondPickPoint.Z));
|
||||
|
||||
_hasAssemblyInstallationReference = true;
|
||||
_assemblyInstallationPickPoint = pickPoint;
|
||||
_assemblyInstallationPickPoint = AssemblyEndFaceAnalyzer.ToPoint3D((result.PickPoint + result.SecondaryPickPoint) * 0.5f);
|
||||
_assemblyInstallationBaseAnchorPoint = AssemblyEndFaceAnalyzer.ToPoint3D(result.AnchorPoint);
|
||||
_assemblyInstallationPlaneNormal = result.PlaneNormal;
|
||||
_assemblyInstallationPlaneSpanDirection = result.PlaneSpanDirection;
|
||||
@ -2242,7 +2295,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters));
|
||||
UpdateAssemblyInstallationAnchorFromVerticalOffset();
|
||||
|
||||
RenderAssemblyInstallationPickPoint(_assemblyInstallationPickPoint);
|
||||
RenderAssemblyInstallationPickPoints(_assemblyInstallationSeedPoints);
|
||||
RenderAssemblyInstallationCenterLine(AssemblyEndFaceAnalyzer.ToPoint3D(result.InstallLineBasePoint), result.OpticalAxisDirection);
|
||||
|
||||
RefreshAssemblyTerminalObjectInfo();
|
||||
@ -2250,14 +2303,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
UpdateMainStatus(
|
||||
$"安装参考已计算:安装点=({_assemblyInstallationAnchorPoint.X:F2}, {_assemblyInstallationAnchorPoint.Y:F2}, {_assemblyInstallationAnchorPoint.Z:F2}),偏距={_assemblyInstallationOffsetDistanceInMeters:F3}m");
|
||||
LogManager.Info(
|
||||
$"[直线装配] 安装参考已计算: 选定安装点=({pickPoint.X:F3}, {pickPoint.Y:F3}, {pickPoint.Z:F3}), " +
|
||||
$"[直线装配] 安装参考已计算: 安装面点1=({firstPickPoint.X:F3}, {firstPickPoint.Y:F3}, {firstPickPoint.Z:F3}), " +
|
||||
$"安装面点2=({secondPickPoint.X:F3}, {secondPickPoint.Y:F3}, {secondPickPoint.Z:F3}), " +
|
||||
$"安装点=({_assemblyInstallationAnchorPoint.X:F3}, {_assemblyInstallationAnchorPoint.Y:F3}, {_assemblyInstallationAnchorPoint.Z:F3}), " +
|
||||
$"偏距={_assemblyInstallationOffsetDistanceInMeters:F3}m, 平面法向=({result.PlaneNormal.X:F4}, {result.PlaneNormal.Y:F4}, {result.PlaneNormal.Z:F4})");
|
||||
OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod));
|
||||
OnPropertyChanged(nameof(AssemblyInstallationPointButtonText));
|
||||
}
|
||||
|
||||
private void RenderAssemblyInstallationPickPoint(Point3D pickPoint)
|
||||
private void RenderAssemblyInstallationPickPoints(IReadOnlyList<Point3D> pickPoints)
|
||||
{
|
||||
var renderPlugin = PathPointRenderPlugin.Instance;
|
||||
if (renderPlugin == null)
|
||||
@ -2271,7 +2325,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
Id = AssemblyInstallationPickPointPathId,
|
||||
Description = "终端安装点"
|
||||
};
|
||||
AddVisualizationPoint(markerRoute, pickPoint, "安装点", PathPointType.WayPoint);
|
||||
for (int i = 0; i < pickPoints.Count; i++)
|
||||
{
|
||||
AddVisualizationPoint(markerRoute, pickPoints[i], $"安装面点{i + 1}", PathPointType.WayPoint);
|
||||
}
|
||||
renderPlugin.RenderPointOnly(markerRoute);
|
||||
}
|
||||
|
||||
@ -2462,6 +2519,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
_assemblyInstallationPlaneNormal = default(Vector3);
|
||||
_assemblyInstallationPlaneSpanDirection = default(Vector3);
|
||||
_assemblyInstallationOffsetDistanceInMeters = 0.0;
|
||||
_assemblyInstallationSeedPoints.Clear();
|
||||
_assemblyAnchorVerticalOffsetInMeters = DefaultAssemblyAnchorVerticalOffsetInMeters;
|
||||
OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters));
|
||||
OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod));
|
||||
|
||||
@ -8,6 +8,7 @@ namespace NavisworksTransport.Utils.GeometryAnalysis
|
||||
public Vector3 OpticalAxisBasePoint { get; set; }
|
||||
public Vector3 OpticalAxisDirection { get; set; }
|
||||
public Vector3 PickPoint { get; set; }
|
||||
public Vector3 SecondaryPickPoint { get; set; }
|
||||
public Vector3 PlaneNormal { get; set; }
|
||||
public Vector3 PlaneSpanDirection { get; set; }
|
||||
public float OffsetDistance { get; set; }
|
||||
@ -57,6 +58,72 @@ namespace NavisworksTransport.Utils.GeometryAnalysis
|
||||
OpticalAxisBasePoint = opticalAxisBasePoint,
|
||||
OpticalAxisDirection = axisDir,
|
||||
PickPoint = pickPoint,
|
||||
SecondaryPickPoint = pickPoint,
|
||||
PlaneNormal = planeNormal,
|
||||
PlaneSpanDirection = planeSpanDirection,
|
||||
OffsetDistance = offsetDistance,
|
||||
InstallLineBasePoint = installLineBasePoint,
|
||||
AnchorPoint = anchorPoint
|
||||
};
|
||||
}
|
||||
|
||||
public static AssemblyInstallationReferenceResult Build(
|
||||
Vector3 opticalAxisBasePoint,
|
||||
Vector3 opticalAxisDirection,
|
||||
Vector3 firstPickPoint,
|
||||
Vector3 secondPickPoint)
|
||||
{
|
||||
float axisLength = opticalAxisDirection.Length();
|
||||
if (axisLength < 1e-6f)
|
||||
{
|
||||
throw new InvalidOperationException("光轴方向长度过小,无法计算安装参考。");
|
||||
}
|
||||
|
||||
Vector3 axisDir = opticalAxisDirection / axisLength;
|
||||
Vector3 pickSegment = secondPickPoint - firstPickPoint;
|
||||
Vector3 axisAlignedComponent = axisDir * Vector3.Dot(pickSegment, axisDir);
|
||||
Vector3 planeSpanDirection = pickSegment - axisAlignedComponent;
|
||||
float spanLength = planeSpanDirection.Length();
|
||||
if (spanLength < MinimumOffsetDistance)
|
||||
{
|
||||
throw new InvalidOperationException("两次安装点拾取几乎与光轴平行,无法确定安装参考面,请重新选择。");
|
||||
}
|
||||
|
||||
planeSpanDirection /= spanLength;
|
||||
|
||||
Vector3 midpoint = (firstPickPoint + secondPickPoint) * 0.5f;
|
||||
Vector3 planeNormal = Vector3.Cross(axisDir, planeSpanDirection);
|
||||
float normalLength = planeNormal.Length();
|
||||
if (normalLength < 1e-6f)
|
||||
{
|
||||
throw new InvalidOperationException("安装参考面法向计算失败,请重新选择。");
|
||||
}
|
||||
|
||||
planeNormal /= normalLength;
|
||||
|
||||
Vector3 midpointOffset = midpoint - opticalAxisBasePoint;
|
||||
float signedOffset = Vector3.Dot(midpointOffset, planeNormal);
|
||||
if (Math.Abs(signedOffset) < MinimumOffsetDistance)
|
||||
{
|
||||
throw new InvalidOperationException("安装面中点距离光轴过小,无法确定安装参考面,请重新选择。");
|
||||
}
|
||||
|
||||
if (signedOffset < 0f)
|
||||
{
|
||||
planeNormal = -planeNormal;
|
||||
signedOffset = -signedOffset;
|
||||
}
|
||||
|
||||
float offsetDistance = signedOffset;
|
||||
Vector3 installLineBasePoint = opticalAxisBasePoint + planeNormal * offsetDistance;
|
||||
Vector3 anchorPoint = ProjectPointToLine(midpoint, installLineBasePoint, axisDir);
|
||||
|
||||
return new AssemblyInstallationReferenceResult
|
||||
{
|
||||
OpticalAxisBasePoint = opticalAxisBasePoint,
|
||||
OpticalAxisDirection = axisDir,
|
||||
PickPoint = firstPickPoint,
|
||||
SecondaryPickPoint = secondPickPoint,
|
||||
PlaneNormal = planeNormal,
|
||||
PlaneSpanDirection = planeSpanDirection,
|
||||
OffsetDistance = offsetDistance,
|
||||
|
||||
@ -17,6 +17,23 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
Vector3 nextPoint,
|
||||
Vector3 canonicalUp,
|
||||
out RailLocalFrame frame)
|
||||
{
|
||||
return TryCreateLocalFrame(
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
canonicalUp,
|
||||
null,
|
||||
out frame);
|
||||
}
|
||||
|
||||
public static bool TryCreateLocalFrame(
|
||||
Vector3 previousPoint,
|
||||
Vector3 currentPoint,
|
||||
Vector3 nextPoint,
|
||||
Vector3 canonicalUp,
|
||||
Vector3? preferredNormal,
|
||||
out RailLocalFrame frame)
|
||||
{
|
||||
frame = null;
|
||||
|
||||
@ -25,6 +42,7 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
canonicalUp,
|
||||
preferredNormal,
|
||||
out var forward,
|
||||
out var lateral,
|
||||
out var up))
|
||||
@ -44,6 +62,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
out Vector3 forward,
|
||||
out Vector3 lateral,
|
||||
out Vector3 up)
|
||||
{
|
||||
return TryCreateBasis(
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
canonicalUp,
|
||||
null,
|
||||
out forward,
|
||||
out lateral,
|
||||
out up);
|
||||
}
|
||||
|
||||
public static bool TryCreateBasis(
|
||||
Vector3 previousPoint,
|
||||
Vector3 currentPoint,
|
||||
Vector3 nextPoint,
|
||||
Vector3 canonicalUp,
|
||||
Vector3? preferredNormal,
|
||||
out Vector3 forward,
|
||||
out Vector3 lateral,
|
||||
out Vector3 up)
|
||||
{
|
||||
forward = default;
|
||||
lateral = default;
|
||||
@ -61,7 +100,13 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
}
|
||||
|
||||
forward = Vector3.Normalize(tangent);
|
||||
Vector3 projectedUp = canonicalUp - Vector3.Dot(canonicalUp, forward) * forward;
|
||||
Vector3 referenceUp = canonicalUp;
|
||||
if (preferredNormal.HasValue && preferredNormal.Value.LengthSquared() >= TangentEpsilon)
|
||||
{
|
||||
referenceUp = Vector3.Normalize(preferredNormal.Value);
|
||||
}
|
||||
|
||||
Vector3 projectedUp = referenceUp - Vector3.Dot(referenceUp, forward) * forward;
|
||||
if (projectedUp.LengthSquared() < TangentEpsilon)
|
||||
{
|
||||
projectedUp = Math.Abs(forward.Z) < 0.9f
|
||||
@ -95,6 +140,7 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
nextPoint,
|
||||
canonicalUp,
|
||||
convention,
|
||||
null,
|
||||
0.0,
|
||||
out rotation);
|
||||
}
|
||||
@ -107,6 +153,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
ModelAxisConvention convention,
|
||||
double localUpRotationDegrees,
|
||||
out Quaternion rotation)
|
||||
{
|
||||
return TryCreateQuaternion(
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
canonicalUp,
|
||||
convention,
|
||||
null,
|
||||
localUpRotationDegrees,
|
||||
out rotation);
|
||||
}
|
||||
|
||||
public static bool TryCreateQuaternion(
|
||||
Vector3 previousPoint,
|
||||
Vector3 currentPoint,
|
||||
Vector3 nextPoint,
|
||||
Vector3 canonicalUp,
|
||||
ModelAxisConvention convention,
|
||||
Vector3? preferredNormal,
|
||||
double localUpRotationDegrees,
|
||||
out Quaternion rotation)
|
||||
{
|
||||
rotation = Quaternion.Identity;
|
||||
|
||||
@ -115,7 +182,15 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
throw new ArgumentNullException(nameof(convention));
|
||||
}
|
||||
|
||||
if (!TryCreateBasis(previousPoint, currentPoint, nextPoint, canonicalUp, out var forward, out _, out var up))
|
||||
if (!TryCreateBasis(
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
canonicalUp,
|
||||
preferredNormal,
|
||||
out var forward,
|
||||
out _,
|
||||
out var up))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
92
src/Utils/CoordinateSystem/ObjectSpaceOrientationHelper.cs
Normal file
92
src/Utils/CoordinateSystem/ObjectSpaceOrientationHelper.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Autodesk.Navisworks.Api;
|
||||
|
||||
namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
{
|
||||
public static class ObjectSpaceOrientationHelper
|
||||
{
|
||||
public static (Vector3D right, Vector3D up) CalculateAxes(
|
||||
Point3D startPoint,
|
||||
Point3D endPoint,
|
||||
Vector3D upReference,
|
||||
Vector3D horizontalDirection = null)
|
||||
{
|
||||
var segmentDirection = new Vector3(
|
||||
(float)(endPoint.X - startPoint.X),
|
||||
(float)(endPoint.Y - startPoint.Y),
|
||||
(float)(endPoint.Z - startPoint.Z));
|
||||
|
||||
var (right, up) = CalculateAxes(
|
||||
segmentDirection,
|
||||
ToNumerics(upReference),
|
||||
horizontalDirection == null ? (Vector3?)null : ToNumerics(horizontalDirection));
|
||||
|
||||
return (ToNavisworks(right), ToNavisworks(up));
|
||||
}
|
||||
|
||||
public static (Vector3 right, Vector3 up) CalculateAxes(
|
||||
Vector3 segmentDirection,
|
||||
Vector3 upReference,
|
||||
Vector3? horizontalDirection = null)
|
||||
{
|
||||
double segmentLength = Math.Sqrt(
|
||||
segmentDirection.X * segmentDirection.X +
|
||||
segmentDirection.Y * segmentDirection.Y +
|
||||
segmentDirection.Z * segmentDirection.Z);
|
||||
|
||||
if (segmentLength < 1e-9)
|
||||
{
|
||||
throw new InvalidOperationException("路径段长度过小,无法计算通行空间方向。");
|
||||
}
|
||||
|
||||
var normalizedDirection = Vector3.Normalize(segmentDirection);
|
||||
var normalizedUpReference = Normalize(upReference);
|
||||
var right = Cross(normalizedDirection, normalizedUpReference);
|
||||
|
||||
double parallelToUp = Math.Abs(Vector3.Dot(normalizedDirection, normalizedUpReference));
|
||||
if (parallelToUp > 0.9)
|
||||
{
|
||||
if (horizontalDirection.HasValue)
|
||||
{
|
||||
right = Cross(horizontalDirection.Value, normalizedUpReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
right = Math.Abs(normalizedUpReference.X) < 0.9f
|
||||
? Normalize(Cross(normalizedUpReference, new Vector3(1f, 0f, 0f)))
|
||||
: Normalize(Cross(normalizedUpReference, new Vector3(0f, 1f, 0f)));
|
||||
}
|
||||
}
|
||||
|
||||
right = Normalize(right);
|
||||
var up = Normalize(Cross(normalizedDirection, right));
|
||||
return (right, up);
|
||||
}
|
||||
|
||||
private static Vector3 Cross(Vector3 a, Vector3 b)
|
||||
{
|
||||
return Vector3.Cross(a, b);
|
||||
}
|
||||
|
||||
private static Vector3 Normalize(Vector3 vector)
|
||||
{
|
||||
if (vector.LengthSquared() < 1e-9f)
|
||||
{
|
||||
return Vector3.Zero;
|
||||
}
|
||||
|
||||
return Vector3.Normalize(vector);
|
||||
}
|
||||
|
||||
private static Vector3 ToNumerics(Vector3D vector)
|
||||
{
|
||||
return new Vector3((float)vector.X, (float)vector.Y, (float)vector.Z);
|
||||
}
|
||||
|
||||
private static Vector3D ToNavisworks(Vector3 vector)
|
||||
{
|
||||
return new Vector3D(vector.X, vector.Y, vector.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,7 +135,7 @@ namespace NavisworksTransport.Utils
|
||||
|
||||
var adapterForRail = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
var canonicalReferencePointForRail = adapterForRail.ToCanonicalPoint(referencePoint);
|
||||
if (!TryCreateCanonicalLocalFrame(previousPoint, referencePoint, nextPoint, out RailLocalFrame frame))
|
||||
if (!TryCreateCanonicalLocalFrame(route, previousPoint, referencePoint, nextPoint, out RailLocalFrame frame))
|
||||
{
|
||||
var canonicalCenterPointForRailFallback = new Point3D(
|
||||
canonicalReferencePointForRail.X,
|
||||
@ -165,6 +165,22 @@ namespace NavisworksTransport.Utils
|
||||
out Matrix3 linearTransform)
|
||||
{
|
||||
return TryCreateRailLinearTransform(
|
||||
null,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
out linearTransform);
|
||||
}
|
||||
|
||||
public static bool TryCreateRailLinearTransform(
|
||||
PathRoute route,
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
out Matrix3 linearTransform)
|
||||
{
|
||||
return TryCreateRailLinearTransform(
|
||||
route,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
@ -177,6 +193,7 @@ namespace NavisworksTransport.Utils
|
||||
/// 通过模型局部轴约定显式指定本地哪个轴代表 forward/up。
|
||||
/// </summary>
|
||||
public static bool TryCreateRailLinearTransform(
|
||||
PathRoute route,
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
@ -184,6 +201,7 @@ namespace NavisworksTransport.Utils
|
||||
out Matrix3 linearTransform)
|
||||
{
|
||||
return TryCreateRailLinearTransform(
|
||||
route,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
@ -193,6 +211,7 @@ namespace NavisworksTransport.Utils
|
||||
}
|
||||
|
||||
public static bool TryCreateRailLinearTransform(
|
||||
PathRoute route,
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
@ -217,6 +236,7 @@ namespace NavisworksTransport.Utils
|
||||
canonicalCurrentPoint,
|
||||
canonicalNextPoint,
|
||||
HostCoordinateAdapter.CanonicalUpVector3,
|
||||
ResolvePreferredNormal(route),
|
||||
out RailLocalFrame frame))
|
||||
{
|
||||
return false;
|
||||
@ -231,6 +251,7 @@ namespace NavisworksTransport.Utils
|
||||
canonicalNextPoint,
|
||||
HostCoordinateAdapter.CanonicalUpVector3,
|
||||
axisConvention,
|
||||
ResolvePreferredNormal(route),
|
||||
localUpRotationDegrees,
|
||||
out var correctedRotation)
|
||||
? correctedRotation
|
||||
@ -270,6 +291,22 @@ namespace NavisworksTransport.Utils
|
||||
out Rotation3D rotation)
|
||||
{
|
||||
return TryCreateRailRotation(
|
||||
null,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
out rotation);
|
||||
}
|
||||
|
||||
public static bool TryCreateRailRotation(
|
||||
PathRoute route,
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
out Rotation3D rotation)
|
||||
{
|
||||
return TryCreateRailRotation(
|
||||
route,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
@ -285,6 +322,24 @@ namespace NavisworksTransport.Utils
|
||||
out Rotation3D rotation)
|
||||
{
|
||||
return TryCreateRailRotation(
|
||||
null,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
axisConvention,
|
||||
out rotation);
|
||||
}
|
||||
|
||||
public static bool TryCreateRailRotation(
|
||||
PathRoute route,
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
ModelAxisConvention axisConvention,
|
||||
out Rotation3D rotation)
|
||||
{
|
||||
return TryCreateRailRotation(
|
||||
route,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
@ -294,6 +349,7 @@ namespace NavisworksTransport.Utils
|
||||
}
|
||||
|
||||
public static bool TryCreateRailRotation(
|
||||
PathRoute route,
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
@ -305,6 +361,7 @@ namespace NavisworksTransport.Utils
|
||||
LogRotationConstructorConventionOnce();
|
||||
|
||||
if (!TryCreateRailLinearTransform(
|
||||
route,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
@ -446,7 +503,7 @@ namespace NavisworksTransport.Utils
|
||||
|
||||
private static Vector3D ResolveRailNormal(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint)
|
||||
{
|
||||
if (TryCreateCanonicalLocalFrame(previousPoint, currentPoint, nextPoint, out RailLocalFrame frame))
|
||||
if (TryCreateCanonicalLocalFrame(null, previousPoint, currentPoint, nextPoint, out RailLocalFrame frame))
|
||||
{
|
||||
return ToNavVector(frame.Normal);
|
||||
}
|
||||
@ -479,6 +536,7 @@ namespace NavisworksTransport.Utils
|
||||
}
|
||||
|
||||
private static bool TryCreateCanonicalLocalFrame(
|
||||
PathRoute route,
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
@ -490,9 +548,37 @@ namespace NavisworksTransport.Utils
|
||||
ToNumerics(adapter.ToCanonicalPoint(currentPoint)),
|
||||
ToNumerics(adapter.ToCanonicalPoint(nextPoint)),
|
||||
HostCoordinateAdapter.CanonicalUpVector3,
|
||||
ResolvePreferredNormal(route),
|
||||
out frame);
|
||||
}
|
||||
|
||||
private static Vector3? ResolvePreferredNormal(PathRoute route)
|
||||
{
|
||||
if (route?.RailPreferredNormal == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var preferredNormal = new Vector3(
|
||||
(float)route.RailPreferredNormal.X,
|
||||
(float)route.RailPreferredNormal.Y,
|
||||
(float)route.RailPreferredNormal.Z);
|
||||
|
||||
if (preferredNormal.LengthSquared() < 1e-9f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
Vector3 canonicalPreferredNormal = adapter.ToCanonicalVector3(preferredNormal);
|
||||
if (canonicalPreferredNormal.LengthSquared() < 1e-9f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Vector3.Normalize(canonicalPreferredNormal);
|
||||
}
|
||||
|
||||
private static Vector3D Normalize(Vector3D vector)
|
||||
{
|
||||
double lengthSquared = vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user