Fix rail assembly normals and tighten rail camera

This commit is contained in:
tian 2026-04-01 20:12:57 +08:00
parent 2e0b26e032
commit e71ea81cdf
7 changed files with 135 additions and 14 deletions

View File

@ -63,6 +63,28 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
Assert.IsFalse(result.IsReliable); Assert.IsFalse(result.IsReliable);
} }
[TestMethod]
public void OrientNormalTowardTarget_ShouldFlip_WhenNormalOpposesTargetDirection()
{
Vector3 orientedNormal = AssemblyEndFaceAnalyzer.OrientNormalTowardTarget(
new Vector3(-1f, 0f, 0f),
Vector3.Zero,
new Vector3(10f, 0f, 0f));
AssertPoint(orientedNormal, 1.0, 0.0, 0.0);
}
[TestMethod]
public void OrientNormalTowardTarget_ShouldKeepDirection_WhenNormalAlreadyFacesTarget()
{
Vector3 orientedNormal = AssemblyEndFaceAnalyzer.OrientNormalTowardTarget(
new Vector3(0f, 1f, 0f),
new Vector3(1f, 2f, 3f),
new Vector3(1f, 5f, 3f));
AssertPoint(orientedNormal, 0.0, 1.0, 0.0);
}
private static IEnumerable<AnalysisTriangle3> CreateRectangleFace(double minX, double maxX, double minY, double maxY, double z) private static IEnumerable<AnalysisTriangle3> CreateRectangleFace(double minX, double maxX, double minY, double maxY, double z)
{ {
yield return new AnalysisTriangle3( yield return new AnalysisTriangle3(

View File

@ -0,0 +1,36 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils;
using System.Numerics;
namespace NavisworksTransport.UnitTests.CoordinateSystem
{
[TestClass]
public class RailPathPoseHelperTests
{
[TestMethod]
public void NormalizePreferredNormalToHostUpHemisphere_ShouldFlip_WhenPreferredNormalOpposesHostUp()
{
Vector3 hostPreferredNormal = new Vector3(-0.1222f, -0.9676f, 0.2211f);
Vector3 normalized = RailPathPoseHelper.NormalizePreferredNormalToHostUpHemisphere(
hostPreferredNormal,
Vector3.UnitY);
Assert.IsTrue(Vector3.Dot(normalized, Vector3.UnitY) > 0f);
Assert.AreEqual(0.1222 / 1.0008249, normalized.X, 1e-4);
Assert.AreEqual(0.9676 / 1.0008249, normalized.Y, 1e-4);
Assert.AreEqual(-0.2211 / 1.0008249, normalized.Z, 1e-4);
}
[TestMethod]
public void NormalizePreferredNormalToHostUpHemisphere_ShouldKeepDirection_WhenPreferredNormalAlreadyMatchesHostUp()
{
Vector3 hostPreferredNormal = new Vector3(0.139f, 0.954f, 0.266f);
Vector3 normalized = RailPathPoseHelper.NormalizePreferredNormalToHostUpHemisphere(
hostPreferredNormal,
Vector3.UnitY);
Assert.IsTrue(Vector3.Dot(normalized, Vector3.UnitY) > 0f);
Assert.IsTrue(Vector3.Dot(Vector3.Normalize(hostPreferredNormal), normalized) > 0.9999f);
}
}
}

View File

@ -21,8 +21,8 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
Assert.AreEqual(8.0, hoisting.MinDistanceMeters, 1e-9); Assert.AreEqual(8.0, hoisting.MinDistanceMeters, 1e-9);
Assert.AreEqual(22.0, hoisting.ElevationDegrees, 1e-9); Assert.AreEqual(22.0, hoisting.ElevationDegrees, 1e-9);
Assert.AreEqual(0.9, rail.DistanceScale, 1e-9); Assert.AreEqual(0.75, rail.DistanceScale, 1e-9);
Assert.AreEqual(6.0, rail.MinDistanceMeters, 1e-9); Assert.AreEqual(4.5, rail.MinDistanceMeters, 1e-9);
Assert.AreEqual(12.0, rail.ElevationDegrees, 1e-9); Assert.AreEqual(12.0, rail.ElevationDegrees, 1e-9);
} }

View File

@ -1775,10 +1775,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
throw new InvalidOperationException("更新 Rail 路径起点失败。"); throw new InvalidOperationException("更新 Rail 路径起点失败。");
} }
route.RailPreferredNormal = new Point3D( route.RailPreferredNormal = CreatePersistedAssemblyPreferredNormal();
_assemblyInstallationPlaneNormal.X,
_assemblyInstallationPlaneNormal.Y,
_assemblyInstallationPlaneNormal.Z);
route.RailNormalOffset = UnitsConverter.ConvertFromMeters(AssemblyAnchorVerticalOffsetInMeters); route.RailNormalOffset = UnitsConverter.ConvertFromMeters(AssemblyAnchorVerticalOffsetInMeters);
route.LastModified = DateTime.Now; route.LastModified = DateTime.Now;
_assemblyInstallationReferenceRouteId = route.Id; _assemblyInstallationReferenceRouteId = route.Id;
@ -2435,10 +2432,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
if (_hasAssemblyInstallationReference) if (_hasAssemblyInstallationReference)
{ {
route.RailPreferredNormal = new Point3D( route.RailPreferredNormal = CreatePersistedAssemblyPreferredNormal();
_assemblyInstallationPlaneNormal.X,
_assemblyInstallationPlaneNormal.Y,
_assemblyInstallationPlaneNormal.Z);
} }
route.AddPoint(new PathPoint(startPoint, "起点", PathPointType.StartPoint)); route.AddPoint(new PathPoint(startPoint, "起点", PathPointType.StartPoint));
@ -2678,6 +2672,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
} }
} }
private Point3D CreatePersistedAssemblyPreferredNormal()
{
HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
Vector3 normalizedPreferredNormal = RailPathPoseHelper.NormalizePreferredNormalToHostUpHemisphere(
_assemblyInstallationPlaneNormal,
adapter.HostUpVector3);
return new Point3D(
normalizedPreferredNormal.X,
normalizedPreferredNormal.Y,
normalizedPreferredNormal.Z);
}
private Vector3 GetCurrentAssemblyOpticalAxisDirection() private Vector3 GetCurrentAssemblyOpticalAxisDirection()
{ {
Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint(); Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint();
@ -3439,18 +3446,26 @@ namespace NavisworksTransport.UI.WPF.ViewModels
} }
Point3D centerPoint = AssemblyEndFaceAnalyzer.ToPoint3D(result.Center); Point3D centerPoint = AssemblyEndFaceAnalyzer.ToPoint3D(result.Center);
Point3D sphereCenterPoint = new Point3D(
AssemblySphereCenterX,
AssemblySphereCenterY,
AssemblySphereCenterZ);
Vector3 orientedNormal = AssemblyEndFaceAnalyzer.OrientNormalTowardTarget(
result.Normal,
new Vector3((float)centerPoint.X, (float)centerPoint.Y, (float)centerPoint.Z),
new Vector3((float)sphereCenterPoint.X, (float)sphereCenterPoint.Y, (float)sphereCenterPoint.Z));
_hasAssemblyEndFaceAnalysis = true; _hasAssemblyEndFaceAnalysis = true;
_assemblyEndFaceCenterPoint = centerPoint; _assemblyEndFaceCenterPoint = centerPoint;
_assemblyEndFaceNormal = result.Normal; _assemblyEndFaceNormal = orientedNormal;
RenderAssemblyEndFaceSeedPoints(); RenderAssemblyEndFaceSeedPoints();
RenderAssemblyEndFaceCenter(centerPoint); RenderAssemblyEndFaceCenter(centerPoint);
RenderAssemblyEndFaceNormal(centerPoint, result.Normal); RenderAssemblyEndFaceNormal(centerPoint, orientedNormal);
RefreshAssemblyTerminalObjectInfo(); RefreshAssemblyTerminalObjectInfo();
RefreshAssemblyReferenceRodIfNeeded(); RefreshAssemblyReferenceRodIfNeeded();
UpdateMainStatus($"端面分析完成:中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2}),候选三角形={result.CandidateTriangleCount}"); UpdateMainStatus($"端面分析完成:中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2}),候选三角形={result.CandidateTriangleCount}");
LogManager.Info( LogManager.Info(
$"[直线装配] 端面分析完成: 中心=({centerPoint.X:F3}, {centerPoint.Y:F3}, {centerPoint.Z:F3}), " + $"[直线装配] 端面分析完成: 中心=({centerPoint.X:F3}, {centerPoint.Y:F3}, {centerPoint.Z:F3}), " +
$"法向=({result.Normal.X:F4}, {result.Normal.Y:F4}, {result.Normal.Z:F4}), " + $"法向=({orientedNormal.X:F4}, {orientedNormal.Y:F4}, {orientedNormal.Z:F4}), " +
$"三角形={result.CandidateTriangleCount}, 顶点={result.CandidateVertexCount}, 偏差={result.MaxPlaneDeviation:F6}"); $"三角形={result.CandidateTriangleCount}, 顶点={result.CandidateVertexCount}, 偏差={result.MaxPlaneDeviation:F6}");
} }

View File

@ -133,6 +133,29 @@ namespace NavisworksTransport.Utils.GeometryAnalysis
return new Point3D(point.X, point.Y, point.Z); return new Point3D(point.X, point.Y, point.Z);
} }
public static Vector3 OrientNormalTowardTarget(Vector3 normal, Vector3 originPoint, Vector3 targetPoint)
{
if (normal.LengthSquared() < 1e-12f)
{
throw new ArgumentException("端面法向长度过小,无法定向。", nameof(normal));
}
Vector3 normalizedNormal = Vector3.Normalize(normal);
Vector3 targetDirection = targetPoint - originPoint;
if (targetDirection.LengthSquared() < 1e-12f)
{
return normalizedNormal;
}
Vector3 normalizedTargetDirection = Vector3.Normalize(targetDirection);
if (Vector3.Dot(normalizedNormal, normalizedTargetDirection) < 0f)
{
normalizedNormal = -normalizedNormal;
}
return normalizedNormal;
}
private static AnalysisTriangle3 ConvertTriangle(Triangle3D triangle) private static AnalysisTriangle3 ConvertTriangle(Triangle3D triangle)
{ {
return new AnalysisTriangle3(ToVector3(triangle.Point1), ToVector3(triangle.Point2), ToVector3(triangle.Point3)); return new AnalysisTriangle3(ToVector3(triangle.Point1), ToVector3(triangle.Point2), ToVector3(triangle.Point3));

View File

@ -15,6 +15,31 @@ namespace NavisworksTransport.Utils
private const double TangentEpsilon = 1e-9; private const double TangentEpsilon = 1e-9;
private static bool _rotationConstructorConventionLogged; private static bool _rotationConstructorConventionLogged;
/// <summary>
/// 将宿主坐标系中的 Rail 首选法向统一到宿主 up 半球,避免新建装配路径把“轨上侧”写反。
/// </summary>
public static Vector3 NormalizePreferredNormalToHostUpHemisphere(Vector3 hostPreferredNormal, Vector3 hostUpVector)
{
if (hostPreferredNormal.LengthSquared() < 1e-9f)
{
throw new ArgumentException("Rail 首选法向长度过小,无法归一化。", nameof(hostPreferredNormal));
}
if (hostUpVector.LengthSquared() < 1e-9f)
{
throw new ArgumentException("宿主 up 向量长度过小,无法归一化。", nameof(hostUpVector));
}
Vector3 normalizedPreferredNormal = Vector3.Normalize(hostPreferredNormal);
Vector3 normalizedHostUp = Vector3.Normalize(hostUpVector);
if (Vector3.Dot(normalizedPreferredNormal, normalizedHostUp) < 0f)
{
normalizedPreferredNormal = -normalizedPreferredNormal;
}
return normalizedPreferredNormal;
}
/// <summary> /// <summary>
/// 计算构件底面中心相对于 Rail 参考点的 Z 偏移(模型单位)。 /// 计算构件底面中心相对于 Rail 参考点的 Z 偏移(模型单位)。
/// </summary> /// </summary>

View File

@ -301,7 +301,7 @@ namespace NavisworksTransport.Utils
case ViewpointStrategy.PathHoistingSelection: case ViewpointStrategy.PathHoistingSelection:
return new PathViewpointProfile(distanceScale: 1.2, minDistanceMeters: 8.0, elevationDegrees: 22.0); return new PathViewpointProfile(distanceScale: 1.2, minDistanceMeters: 8.0, elevationDegrees: 22.0);
case ViewpointStrategy.PathRailSelection: case ViewpointStrategy.PathRailSelection:
return new PathViewpointProfile(distanceScale: 0.9, minDistanceMeters: 6.0, elevationDegrees: 12.0); return new PathViewpointProfile(distanceScale: 0.75, minDistanceMeters: 4.5, elevationDegrees: 12.0);
case ViewpointStrategy.PathGroundSelection: case ViewpointStrategy.PathGroundSelection:
default: default:
return new PathViewpointProfile(distanceScale: 1.0, minDistanceMeters: 12.0, elevationDegrees: 90.0); return new PathViewpointProfile(distanceScale: 1.0, minDistanceMeters: 12.0, elevationDegrees: 90.0);