diff --git a/NavisworksTransport.UnitTests.csproj b/NavisworksTransport.UnitTests.csproj
index eb50839..6ae959b 100644
--- a/NavisworksTransport.UnitTests.csproj
+++ b/NavisworksTransport.UnitTests.csproj
@@ -62,6 +62,7 @@
+
diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj
index bc1bef4..ce75558 100644
--- a/TransportPlugin.csproj
+++ b/TransportPlugin.csproj
@@ -140,6 +140,7 @@
+
diff --git a/UnitTests/CoordinateSystem/AssemblyInstallationReferenceBuilderTests.cs b/UnitTests/CoordinateSystem/AssemblyInstallationReferenceBuilderTests.cs
new file mode 100644
index 0000000..55f7396
--- /dev/null
+++ b/UnitTests/CoordinateSystem/AssemblyInstallationReferenceBuilderTests.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Numerics;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NavisworksTransport.Utils.GeometryAnalysis;
+
+namespace NavisworksTransport.UnitTests.CoordinateSystem
+{
+ [TestClass]
+ public class AssemblyInstallationReferenceBuilderTests
+ {
+ [TestMethod]
+ public void Build_ShouldCreatePlaneParallelToOpticalAxisAndProjectedAnchor()
+ {
+ Vector3 axisBase = new Vector3(0f, 0f, 0f);
+ Vector3 axisDirection = Vector3.UnitX;
+ Vector3 pickPoint = new Vector3(2f, 3f, 4f);
+
+ AssemblyInstallationReferenceResult result =
+ AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint);
+
+ Assert.AreEqual(5f, result.OffsetDistance, 1e-4f);
+ Assert.AreEqual(2f, result.AnchorPoint.X, 1e-4f);
+ Assert.AreEqual(3f, result.AnchorPoint.Y, 1e-4f);
+ Assert.AreEqual(4f, 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);
+ }
+
+ [TestMethod]
+ public void Build_WhenPickPointTooCloseToOpticalAxis_ShouldThrow()
+ {
+ Vector3 axisBase = new Vector3(0f, 0f, 0f);
+ Vector3 axisDirection = Vector3.UnitX;
+ Vector3 pickPoint = new Vector3(2f, 0f, 0f);
+
+ InvalidOperationException ex = Assert.ThrowsException(
+ () => AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint));
+
+ StringAssert.Contains(ex.Message, "距离光轴过小");
+ }
+ }
+}
diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs
index 04a1fbc..36b7d80 100644
--- a/src/Core/PathPointRenderPlugin.cs
+++ b/src/Core/PathPointRenderPlugin.cs
@@ -133,6 +133,11 @@ namespace NavisworksTransport
///
AssemblyGuideLine,
+ ///
+ /// 直线装配安装参考面样式(绿色半透明)
+ ///
+ AssemblyInstallationPlane,
+
///
/// 吊装路径样式(紫色)
///
@@ -338,6 +343,11 @@ namespace NavisworksTransport
///
public List ObjectSpaceMarkers { get; set; }
+ ///
+ /// 平面标记集合
+ ///
+ public List PlaneMarkers { get; set; }
+
///
/// 是否显示控制点可视化(用户意图)
///
@@ -358,6 +368,7 @@ namespace NavisworksTransport
PathLineMarkers = new List();
TangentMarkers = new List();
ObjectSpaceMarkers = new List();
+ PlaneMarkers = new List();
LastUpdated = DateTime.Now;
}
@@ -633,6 +644,11 @@ namespace NavisworksTransport
RenderPointMarker(graphics, pointMarker);
}
+ foreach (var planeMarker in visualization.PlaneMarkers)
+ {
+ RenderPlaneMarker(graphics, planeMarker);
+ }
+
// 渲染控制点连线(半透明)
foreach (var controlLineMarker in visualization.ControlLineMarkers)
{
@@ -802,6 +818,55 @@ namespace NavisworksTransport
}
}
+ public void RenderRectanglePlane(
+ string pathId,
+ Point3D origin,
+ Vector3D xVector,
+ Vector3D yVector,
+ RenderStyleName renderStyleName,
+ bool filled = true)
+ {
+ if (string.IsNullOrWhiteSpace(pathId))
+ {
+ throw new ArgumentException("pathId 不能为空。", nameof(pathId));
+ }
+
+ try
+ {
+ var visualization = new PathVisualization
+ {
+ PathId = pathId,
+ PathRoute = new PathRoute(pathId)
+ {
+ Id = pathId,
+ Description = $"矩形平面可视化:{pathId}"
+ }
+ };
+
+ var renderStyle = GetRenderStyle(renderStyleName);
+ visualization.PlaneMarkers.Add(new PlaneMarker
+ {
+ Origin = origin,
+ XVector = xVector,
+ YVector = yVector,
+ Color = renderStyle.Color,
+ Alpha = renderStyle.Alpha,
+ Filled = filled
+ });
+
+ lock (_lockObject)
+ {
+ _pathVisualizations[pathId] = visualization;
+ }
+
+ RequestViewRefresh();
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[平面渲染] 渲染矩形平面失败: {ex.Message}", ex);
+ }
+ }
+
///
/// 清除网格可视化
///
@@ -960,6 +1025,7 @@ namespace NavisworksTransport
visualization.PointMarkers.Clear();
visualization.ControlLineMarkers.Clear(); // 确保没有控制点连线
visualization.PathLineMarkers.Clear(); // 确保没有路径连线
+ visualization.PlaneMarkers.Clear();
var points = visualization.PathRoute.Points;
if (points.Count == 0) return;
@@ -2347,6 +2413,9 @@ namespace NavisworksTransport
case RenderStyleName.AssemblyGuideLine:
return new RenderStyle(Color.FromByteRGB(76, 175, 80), 0.85); // Material Green装配基准向量
+ case RenderStyleName.AssemblyInstallationPlane:
+ return new RenderStyle(Color.FromByteRGB(76, 175, 80), 0.5); // Material Green安装参考面,50%透明
+
case RenderStyleName.HoistingLine:
return new RenderStyle(Color.FromByteRGB(156, 39, 176), 0.8); // Material Purple吊装路径,20%透明
@@ -3151,6 +3220,12 @@ namespace NavisworksTransport
}
}
+ private void RenderPlaneMarker(Graphics graphics, PlaneMarker marker)
+ {
+ graphics.Color(marker.Color, marker.Alpha);
+ graphics.Rectangle(marker.Origin, marker.XVector, marker.YVector, marker.Filled);
+ }
+
///
/// 渲染连线标记(支持直线段和圆弧段)
///
@@ -3512,4 +3587,14 @@ namespace NavisworksTransport
return $"CircleMarker[序号={SequenceNumber}, 类型={PointType}, 中心=({Center.X:F2},{Center.Y:F2},{Center.Z:F2}), 半径={Radius:F2}]";
}
}
+
+ public class PlaneMarker
+ {
+ public Point3D Origin { get; set; }
+ public Vector3D XVector { get; set; }
+ public Vector3D YVector { get; set; }
+ public Color Color { get; set; }
+ public double Alpha { get; set; }
+ public bool Filled { get; set; } = true;
+ }
}
diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs
index ef84caa..1335e08 100644
--- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs
+++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs
@@ -141,11 +141,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private bool _hasAssemblyTerminalObject;
private bool _isSelectingAssemblyStartPoint;
private bool _isSelectingAssemblyEndFacePoints;
+ private bool _isSelectingAssemblyInstallationPoint;
private bool _hasAssemblyEndFaceAnalysis;
+ private bool _hasAssemblyInstallationReference;
private ModelItem _assemblyTerminalObject;
private Point3D _assemblyStartPoint;
private Point3D _assemblyEndFaceCenterPoint;
private Vector3 _assemblyEndFaceNormal;
+ private Point3D _assemblyInstallationPickPoint;
+ private Point3D _assemblyInstallationBaseAnchorPoint;
+ private Point3D _assemblyInstallationAnchorPoint;
+ private Vector3 _assemblyInstallationPlaneNormal;
+ private Vector3 _assemblyInstallationPlaneSpanDirection;
+ private double _assemblyInstallationOffsetDistanceInMeters;
private readonly List _assemblyEndFaceSeedPoints = new List();
private const string AssemblyAnchorMarkerPathId = "assembly_anchor_marker";
private const string AssemblyCenterGuideLinePathId = "assembly_center_guide_line";
@@ -153,6 +161,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private const string AssemblyEndFaceSeedPathId = "assembly_end_face_seed_points";
private const string AssemblyEndFaceCenterPathId = "assembly_end_face_center";
private const string AssemblyEndFaceNormalPathId = "assembly_end_face_normal";
+ private const string AssemblyInstallationPickPointPathId = "assembly_install_pick_point";
+ private const string AssemblyInstallationAnchorPointPathId = "assembly_install_anchor_point";
+ private const string AssemblyInstallationCenterLinePathId = "assembly_install_center_line";
+ private const string AssemblyInstallationOffsetLinePathId = "assembly_install_offset_line";
+ private const string AssemblyInstallationPlanePathId = "assembly_install_plane";
+ private const double AssemblyInstallationPlanePaddingInMeters = 0.5;
+ private const double AssemblyInstallationLineLengthScale = 1.2;
// 自动路径起点和终点的路径对象引用(用于正确的ID管理)
private PathRoute _autoPathStartPointRoute = null;
@@ -375,6 +390,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
if (SetProperty(ref _assemblyAnchorVerticalOffsetInMeters, value) && HasAssemblyTerminalObject)
{
+ if (_hasAssemblyInstallationReference)
+ {
+ UpdateAssemblyInstallationAnchorFromVerticalOffset();
+ }
RefreshAssemblyTerminalObjectInfo();
RenderAssemblyAnchorMarker();
RefreshAssemblyReferenceRodIfNeeded();
@@ -969,6 +988,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand CaptureAssemblyTerminalObjectCommand { get; private set; }
public ICommand GenerateAssemblyReferenceRodCommand { get; private set; }
public ICommand SelectAssemblyStartPointCommand { get; private set; }
+ public ICommand SelectAssemblyInstallationPointCommand { get; private set; }
public ICommand ClearAssemblyReferenceRodCommand { get; private set; }
public ICommand AnalyzeAssemblyTerminalFaceCommand { get; private set; }
@@ -1007,16 +1027,27 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public bool CanExecuteSaveAsPath => SelectedPathRoute != null && SelectedPathRoute.Points.Count > 0;
public bool CanGenerateAssemblyReferenceRod => HasAssemblyTerminalObject &&
+ _hasAssemblyInstallationReference &&
AssemblyReferenceRodLengthInMeters > 0 &&
AssemblyReferenceRodDiameterInMeters > 0;
public bool CanSelectAssemblyStartPoint => HasAssemblyTerminalObject &&
_pathPlanningManager != null &&
AssemblyReferencePathManager.Instance.HasReferenceLine &&
!IsSelectingAssemblyStartPoint;
+ public bool CanSelectAssemblyInstallationPoint => HasAssemblyTerminalObject &&
+ _pathPlanningManager != null &&
+ !_isSelectingAssemblyStartPoint &&
+ !_isSelectingAssemblyEndFacePoints &&
+ !_isSelectingAssemblyInstallationPoint;
public bool CanAnalyzeAssemblyTerminalFace => HasAssemblyTerminalObject &&
_pathPlanningManager != null &&
!IsSelectingAssemblyStartPoint &&
- !IsSelectingAssemblyEndFacePoints;
+ !IsSelectingAssemblyEndFacePoints &&
+ !_isSelectingAssemblyInstallationPoint;
+
+ public string AssemblyInstallationPointButtonText => _hasAssemblyInstallationReference
+ ? "重选安装点"
+ : "选安装点";
public bool IsSelectingAssemblyEndFacePoints => _isSelectingAssemblyEndFacePoints;
@@ -1301,6 +1332,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CaptureAssemblyTerminalObjectCommand = new RelayCommand(async () => await ExecuteCaptureAssemblyTerminalObjectAsync());
GenerateAssemblyReferenceRodCommand = new RelayCommand(async () => await ExecuteGenerateAssemblyReferenceRodAsync(), () => CanGenerateAssemblyReferenceRod);
SelectAssemblyStartPointCommand = new RelayCommand(async () => await ExecuteSelectAssemblyStartPointAsync(), () => CanSelectAssemblyStartPoint);
+ SelectAssemblyInstallationPointCommand = new RelayCommand(async () => await ExecuteSelectAssemblyInstallationPointAsync(), () => CanSelectAssemblyInstallationPoint);
ClearAssemblyReferenceRodCommand = new RelayCommand(() => ExecuteClearAssemblyReferenceRod());
AnalyzeAssemblyTerminalFaceCommand = new RelayCommand(async () => await ExecuteAnalyzeAssemblyTerminalFaceAsync(), () => CanAnalyzeAssemblyTerminalFace);
}
@@ -1332,12 +1364,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
HasAssemblyTerminalObject = true;
AssemblyStartPointText = "未选择";
AssemblyTerminalObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(selectedItem);
- InitializeAssemblyAnchorVerticalOffsetFromTerminalObject();
+ ResetAssemblyEndFaceAnalysisState();
+ ResetAssemblyInstallationReferenceState();
RefreshAssemblyTerminalObjectInfo();
- RenderAssemblyAnchorMarker();
+ ClearAssemblyAnchorMarker();
ClearAssemblyEndFaceAnalysisVisuals();
+ ClearAssemblyInstallationReferenceVisuals();
OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod));
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
+ OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint));
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
UpdateMainStatus($"已捕获终点箱体: {AssemblyTerminalObjectName}");
LogManager.Info($"[直线装配] 已捕获终点箱体: {AssemblyTerminalObjectName}");
@@ -1450,13 +1485,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CleanupAssemblyReferenceSelection();
CleanupAssemblyEndFaceSelection(clearVisuals: false);
+ CleanupAssemblyInstallationSelection();
ClearAssemblyEndFaceAnalysisVisuals();
+ ResetAssemblyEndFaceAnalysisState();
+ ClearAssemblyInstallationReferenceVisuals();
+ ResetAssemblyInstallationReferenceState();
_assemblyEndFaceSeedPoints.Clear();
_pathPlanningManager.DisableMouseHandling();
_isSelectingAssemblyEndFacePoints = true;
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
+ OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint));
PathClickToolPlugin.MouseClicked -= OnAssemblyEndFaceMouseClicked;
PathClickToolPlugin.MouseClicked += OnAssemblyEndFaceMouseClicked;
@@ -1472,6 +1512,45 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}, "分析终端端面");
}
+ private async Task ExecuteSelectAssemblyInstallationPointAsync()
+ {
+ await SafeExecuteAsync(() =>
+ {
+ if (_pathPlanningManager == null)
+ {
+ throw new InvalidOperationException("路径规划管理器未初始化,无法选择安装点。");
+ }
+
+ if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
+ {
+ throw new InvalidOperationException("终点箱体未设置或已失效,请先捕获终点箱体。");
+ }
+
+ CleanupAssemblyReferenceSelection();
+ CleanupAssemblyEndFaceSelection(clearVisuals: false);
+ CleanupAssemblyInstallationSelection();
+ ClearAssemblyInstallationReferenceVisuals();
+
+ _pathPlanningManager.DisableMouseHandling();
+ _isSelectingAssemblyInstallationPoint = true;
+ OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
+ OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
+ OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint));
+
+ PathClickToolPlugin.MouseClicked -= OnAssemblyInstallationMouseClicked;
+ PathClickToolPlugin.MouseClicked += OnAssemblyInstallationMouseClicked;
+
+ if (!ForceReinitializeToolPlugin(subscribeToEvents: false))
+ {
+ CleanupAssemblyInstallationSelection();
+ throw new InvalidOperationException("ToolPlugin 初始化失败,请重试。");
+ }
+
+ UpdateMainStatus("请在终点箱体表面点击安装点,系统将按该点计算安装参考面和终点锚点。");
+ LogManager.Info("[直线装配] 已进入安装点拾取模式");
+ }, "选择安装点");
+ }
+
private async void OnAssemblyReferenceMouseClicked(object sender, PickItemResult pickResult)
{
try
@@ -1544,6 +1623,35 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
+ private async void OnAssemblyInstallationMouseClicked(object sender, PickItemResult pickResult)
+ {
+ try
+ {
+ if (!_isSelectingAssemblyInstallationPoint || pickResult == null)
+ {
+ return;
+ }
+
+ await SafeExecuteAsync(() =>
+ {
+ if (!IsPickOnAssemblyTerminalObject(pickResult))
+ {
+ UpdateMainStatus("请点击当前终点箱体表面,不要点到其他对象。");
+ return;
+ }
+
+ BuildAndRenderAssemblyInstallationReference(pickResult.Point);
+ CleanupAssemblyInstallationSelection();
+ }, "处理安装点拾取");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[直线装配] 安装点计算失败: {ex.Message}", ex);
+ UpdateMainStatus($"安装点计算失败: {ex.Message}");
+ CleanupAssemblyInstallationSelection();
+ }
+ }
+
private void CreateAssemblyLinearRoute(Point3D startPoint)
{
Point3D endPoint = AssemblyReferencePathManager.Instance.ReferenceLineEnd;
@@ -1610,11 +1718,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels
ClearAssemblyCenterGuideLine();
ClearAssemblyReferenceLine();
ClearAssemblyEndFaceAnalysisVisuals();
+ ClearAssemblyInstallationReferenceVisuals();
if (resetStartPointText)
{
AssemblyStartPointText = "未选择";
}
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
+ OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint));
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
if (!string.IsNullOrWhiteSpace(statusMessage))
{
@@ -1634,6 +1744,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
throw new InvalidOperationException("终点箱体未设置或已失效,无法计算对接点");
}
+ if (_hasAssemblyInstallationReference && _assemblyInstallationAnchorPoint != null)
+ {
+ return _assemblyInstallationAnchorPoint;
+ }
+
HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter);
@@ -1654,6 +1769,39 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return adapter.FromCanonicalPoint(canonicalAnchorPoint);
}
+ private void UpdateAssemblyInstallationAnchorFromVerticalOffset()
+ {
+ if (!_hasAssemblyInstallationReference ||
+ _assemblyInstallationBaseAnchorPoint == null)
+ {
+ return;
+ }
+
+ double offset = UnitsConverter.ConvertFromMeters(_assemblyAnchorVerticalOffsetInMeters);
+ _assemblyInstallationAnchorPoint = new Point3D(
+ _assemblyInstallationBaseAnchorPoint.X + _assemblyInstallationPlaneNormal.X * (float)offset,
+ _assemblyInstallationBaseAnchorPoint.Y + _assemblyInstallationPlaneNormal.Y * (float)offset,
+ _assemblyInstallationBaseAnchorPoint.Z + _assemblyInstallationPlaneNormal.Z * (float)offset);
+
+ RenderAssemblyInstallationAnchorPoint(_assemblyInstallationAnchorPoint);
+ RenderAssemblyInstallationPlane(_assemblyInstallationAnchorPoint, GetCurrentAssemblyOpticalAxisDirection(), _assemblyInstallationPlaneSpanDirection);
+ }
+
+ private Vector3 GetCurrentAssemblyOpticalAxisDirection()
+ {
+ Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint();
+ Point3D sphereCenterPoint = new Point3D(
+ AssemblySphereCenterX,
+ AssemblySphereCenterY,
+ AssemblySphereCenterZ);
+
+ Vector3 opticalAxisDirection = new Vector3(
+ (float)(opticalAxisReferencePoint.X - sphereCenterPoint.X),
+ (float)(opticalAxisReferencePoint.Y - sphereCenterPoint.Y),
+ (float)(opticalAxisReferencePoint.Z - sphereCenterPoint.Z));
+ return Vector3.Normalize(opticalAxisDirection);
+ }
+
private Point3D GetAssemblyOpticalAxisReferencePoint()
{
if (_hasAssemblyEndFaceAnalysis && _assemblyEndFaceCenterPoint != null)
@@ -1719,18 +1867,35 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
var bounds = _assemblyTerminalObject.BoundingBox();
- Point3D anchorPoint = GetAssemblyTerminalAnchorPoint();
string anchorText = GetAssemblyAnchorText();
string mountText = AssemblyMountMode == RailMountMode.OverRail ? "轨上安装" : "轨下安装";
Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint();
string opticalAxisText = _hasAssemblyEndFaceAnalysis
? string.Format("端面中心=({0:F2}, {1:F2}, {2:F2})", opticalAxisReferencePoint.X, opticalAxisReferencePoint.Y, opticalAxisReferencePoint.Z)
: string.Format("箱体中心=({0:F2}, {1:F2}, {2:F2})", opticalAxisReferencePoint.X, opticalAxisReferencePoint.Y, opticalAxisReferencePoint.Z);
+ string anchorPointText = _hasAssemblyInstallationReference
+ ? string.Format("{0}点=({1:F2}, {2:F2}, {3:F2})",
+ anchorText,
+ _assemblyInstallationAnchorPoint.X,
+ _assemblyInstallationAnchorPoint.Y,
+ _assemblyInstallationAnchorPoint.Z)
+ : string.Format("{0}点=未选择", anchorText);
+ string installationText = _hasAssemblyInstallationReference
+ ? string.Format(
+ "选定安装点=({0:F2}, {1:F2}, {2:F2}),安装点=({3:F2}, {4:F2}, {5:F2}),偏距={6:F3}m",
+ _assemblyInstallationPickPoint.X,
+ _assemblyInstallationPickPoint.Y,
+ _assemblyInstallationPickPoint.Z,
+ _assemblyInstallationAnchorPoint.X,
+ _assemblyInstallationAnchorPoint.Y,
+ _assemblyInstallationAnchorPoint.Z,
+ _assemblyInstallationOffsetDistanceInMeters)
+ : "安装点=未选择";
if (referenceStartPoint != null && referenceEndPoint != null)
{
AssemblyTerminalObjectInfo = string.Format(
- "中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7}点=({8:F2}, {9:F2}, {10:F2}),{11},垂直偏移={12:F3}m,辅助线外端=({13:F2}, {14:F2}, {15:F2})",
+ "中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7},{8},{9},垂直偏移={10:F3}m,辅助线外端=({11:F2}, {12:F2}, {13:F2})",
bounds.Center.X,
bounds.Center.Y,
bounds.Center.Z,
@@ -1738,11 +1903,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
bounds.Max.Y - bounds.Min.Y,
bounds.Max.Z - bounds.Min.Z,
mountText,
- anchorText,
- referenceEndPoint.X,
- referenceEndPoint.Y,
- referenceEndPoint.Z,
+ anchorPointText,
opticalAxisText,
+ installationText,
AssemblyAnchorVerticalOffsetInMeters,
referenceStartPoint.X,
referenceStartPoint.Y,
@@ -1751,7 +1914,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
AssemblyTerminalObjectInfo = string.Format(
- "中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7}点=({8:F2}, {9:F2}, {10:F2}),{11},垂直偏移={12:F3}m",
+ "中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7},{8},{9},垂直偏移={10:F3}m",
bounds.Center.X,
bounds.Center.Y,
bounds.Center.Z,
@@ -1759,11 +1922,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
bounds.Max.Y - bounds.Min.Y,
bounds.Max.Z - bounds.Min.Z,
mountText,
- anchorText,
- anchorPoint.X,
- anchorPoint.Y,
- anchorPoint.Z,
+ anchorPointText,
opticalAxisText,
+ installationText,
AssemblyAnchorVerticalOffsetInMeters);
}
@@ -2015,7 +2176,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
Id = AssemblyEndFaceCenterPathId,
Description = "终端安装端面中心"
};
- AddVisualizationPoint(markerRoute, centerPoint, "端面中心", PathPointType.EndPoint);
+ AddVisualizationPoint(markerRoute, centerPoint, "端面中心", PathPointType.WayPoint);
renderPlugin.RenderPointOnly(markerRoute);
}
@@ -2053,6 +2214,158 @@ namespace NavisworksTransport.UI.WPF.ViewModels
RenderStyleName.AssemblyGuideLine);
}
+ private void BuildAndRenderAssemblyInstallationReference(Point3D pickPoint)
+ {
+ Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint();
+ Point3D sphereCenterPoint = new Point3D(
+ AssemblySphereCenterX,
+ AssemblySphereCenterY,
+ AssemblySphereCenterZ);
+
+ Vector3 opticalAxisDirection = new Vector3(
+ (float)(opticalAxisReferencePoint.X - sphereCenterPoint.X),
+ (float)(opticalAxisReferencePoint.Y - sphereCenterPoint.Y),
+ (float)(opticalAxisReferencePoint.Z - sphereCenterPoint.Z));
+
+ 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));
+
+ _hasAssemblyInstallationReference = true;
+ _assemblyInstallationPickPoint = pickPoint;
+ _assemblyInstallationBaseAnchorPoint = AssemblyEndFaceAnalyzer.ToPoint3D(result.AnchorPoint);
+ _assemblyInstallationPlaneNormal = result.PlaneNormal;
+ _assemblyInstallationPlaneSpanDirection = result.PlaneSpanDirection;
+ _assemblyInstallationOffsetDistanceInMeters = UnitsConverter.ConvertToMeters(result.OffsetDistance);
+ _assemblyAnchorVerticalOffsetInMeters = DefaultAssemblyAnchorVerticalOffsetInMeters;
+ OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters));
+ UpdateAssemblyInstallationAnchorFromVerticalOffset();
+
+ RenderAssemblyInstallationPickPoint(_assemblyInstallationPickPoint);
+ RenderAssemblyInstallationCenterLine(AssemblyEndFaceAnalyzer.ToPoint3D(result.InstallLineBasePoint), result.OpticalAxisDirection);
+
+ RefreshAssemblyTerminalObjectInfo();
+ RefreshAssemblyReferenceRodIfNeeded();
+ UpdateMainStatus(
+ $"安装参考已计算:安装点=({_assemblyInstallationAnchorPoint.X:F2}, {_assemblyInstallationAnchorPoint.Y:F2}, {_assemblyInstallationAnchorPoint.Z:F2}),偏距={_assemblyInstallationOffsetDistanceInMeters:F3}m");
+ LogManager.Info(
+ $"[直线装配] 安装参考已计算: 选定安装点=({pickPoint.X:F3}, {pickPoint.Y:F3}, {pickPoint.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)
+ {
+ var renderPlugin = PathPointRenderPlugin.Instance;
+ if (renderPlugin == null)
+ {
+ return;
+ }
+
+ renderPlugin.RemovePath(AssemblyInstallationPickPointPathId);
+ var markerRoute = new PathRoute("安装点")
+ {
+ Id = AssemblyInstallationPickPointPathId,
+ Description = "终端安装点"
+ };
+ AddVisualizationPoint(markerRoute, pickPoint, "安装点", PathPointType.WayPoint);
+ renderPlugin.RenderPointOnly(markerRoute);
+ }
+
+ private void RenderAssemblyInstallationAnchorPoint(Point3D anchorPoint)
+ {
+ var renderPlugin = PathPointRenderPlugin.Instance;
+ if (renderPlugin == null)
+ {
+ return;
+ }
+
+ renderPlugin.RemovePath(AssemblyInstallationAnchorPointPathId);
+ var markerRoute = new PathRoute("安装点")
+ {
+ Id = AssemblyInstallationAnchorPointPathId,
+ Description = "终端安装点"
+ };
+ AddVisualizationPoint(markerRoute, anchorPoint, "安装点", PathPointType.WayPoint);
+ renderPlugin.RenderPointOnly(markerRoute);
+ }
+
+ private void RenderAssemblyInstallationCenterLine(Point3D lineBasePoint, Vector3 axisDirection)
+ {
+ var renderPlugin = PathPointRenderPlugin.Instance;
+ if (renderPlugin == null)
+ {
+ return;
+ }
+
+ renderPlugin.ClearRailBaseline(AssemblyInstallationCenterLinePathId);
+ double halfLength = GetAssemblyInstallationPlaneSideLength() * AssemblyInstallationLineLengthScale / 2.0;
+ Point3D startPoint = new Point3D(
+ lineBasePoint.X - axisDirection.X * (float)halfLength,
+ lineBasePoint.Y - axisDirection.Y * (float)halfLength,
+ lineBasePoint.Z - axisDirection.Z * (float)halfLength);
+ Point3D endPoint = new Point3D(
+ lineBasePoint.X + axisDirection.X * (float)halfLength,
+ lineBasePoint.Y + axisDirection.Y * (float)halfLength,
+ lineBasePoint.Z + axisDirection.Z * (float)halfLength);
+ renderPlugin.RenderRailBaseline(
+ AssemblyInstallationCenterLinePathId,
+ new List { startPoint, endPoint },
+ RenderStyleName.AssemblyGuideLine);
+ }
+
+ private void RenderAssemblyInstallationPlane(Point3D planeCenter, Vector3 axisDirection, Vector3 planeSpanDirection)
+ {
+ var renderPlugin = PathPointRenderPlugin.Instance;
+ if (renderPlugin == null)
+ {
+ return;
+ }
+
+ double sideLength = GetAssemblyInstallationPlaneSideLength();
+ double halfLength = sideLength / 2.0;
+ double thickness = UnitsConverter.ConvertFromMeters(0.01);
+
+ Vector3D xVector = new Vector3D(
+ axisDirection.X * (float)sideLength,
+ axisDirection.Y * (float)sideLength,
+ axisDirection.Z * (float)sideLength);
+ Vector3D yVector = new Vector3D(
+ planeSpanDirection.X * (float)sideLength,
+ planeSpanDirection.Y * (float)sideLength,
+ planeSpanDirection.Z * (float)sideLength);
+ Point3D origin = new Point3D(
+ planeCenter.X - axisDirection.X * (float)halfLength - planeSpanDirection.X * (float)halfLength + _assemblyInstallationPlaneNormal.X * thickness,
+ planeCenter.Y - axisDirection.Y * (float)halfLength - planeSpanDirection.Y * (float)halfLength + _assemblyInstallationPlaneNormal.Y * thickness,
+ planeCenter.Z - axisDirection.Z * (float)halfLength - planeSpanDirection.Z * (float)halfLength + _assemblyInstallationPlaneNormal.Z * thickness);
+
+ renderPlugin.RemovePath(AssemblyInstallationPlanePathId);
+ renderPlugin.RenderRectanglePlane(
+ AssemblyInstallationPlanePathId,
+ origin,
+ xVector,
+ yVector,
+ RenderStyleName.AssemblyInstallationPlane,
+ filled: true);
+ }
+
+ private double GetAssemblyInstallationPlaneSideLength()
+ {
+ if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
+ {
+ return UnitsConverter.ConvertFromMeters(1.0);
+ }
+
+ BoundingBox3D bounds = _assemblyTerminalObject.BoundingBox();
+ double maxSize = Math.Max(
+ bounds.Max.X - bounds.Min.X,
+ Math.Max(bounds.Max.Y - bounds.Min.Y, bounds.Max.Z - bounds.Min.Z));
+ return maxSize + UnitsConverter.ConvertFromMeters(AssemblyInstallationPlanePaddingInMeters) * 2.0;
+ }
+
private ProjectReferenceFrame CreateAssemblyProjectReferenceFrame(HostCoordinateAdapter adapter)
{
if (adapter == null)
@@ -2116,11 +2429,45 @@ namespace NavisworksTransport.UI.WPF.ViewModels
renderPlugin.RemovePath(AssemblyEndFaceSeedPathId);
renderPlugin.RemovePath(AssemblyEndFaceCenterPathId);
renderPlugin.ClearRailBaseline(AssemblyEndFaceNormalPathId);
+ }
+
+ private void ResetAssemblyEndFaceAnalysisState()
+ {
_hasAssemblyEndFaceAnalysis = false;
_assemblyEndFaceCenterPoint = null;
_assemblyEndFaceNormal = default(Vector3);
}
+ private void ClearAssemblyInstallationReferenceVisuals()
+ {
+ var renderPlugin = PathPointRenderPlugin.Instance;
+ if (renderPlugin == null)
+ {
+ return;
+ }
+
+ renderPlugin.RemovePath(AssemblyInstallationPickPointPathId);
+ renderPlugin.RemovePath(AssemblyInstallationAnchorPointPathId);
+ renderPlugin.ClearRailBaseline(AssemblyInstallationCenterLinePathId);
+ renderPlugin.ClearRailBaseline(AssemblyInstallationOffsetLinePathId);
+ renderPlugin.RemovePath(AssemblyInstallationPlanePathId);
+ }
+
+ private void ResetAssemblyInstallationReferenceState()
+ {
+ _hasAssemblyInstallationReference = false;
+ _assemblyInstallationPickPoint = null;
+ _assemblyInstallationBaseAnchorPoint = null;
+ _assemblyInstallationAnchorPoint = null;
+ _assemblyInstallationPlaneNormal = default(Vector3);
+ _assemblyInstallationPlaneSpanDirection = default(Vector3);
+ _assemblyInstallationOffsetDistanceInMeters = 0.0;
+ _assemblyAnchorVerticalOffsetInMeters = DefaultAssemblyAnchorVerticalOffsetInMeters;
+ OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters));
+ OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod));
+ OnPropertyChanged(nameof(AssemblyInstallationPointButtonText));
+ }
+
private void CleanupAssemblyReferenceSelection()
{
try
@@ -2137,6 +2484,25 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
+ private void CleanupAssemblyInstallationSelection()
+ {
+ try
+ {
+ PathClickToolPlugin.MouseClicked -= OnAssemblyInstallationMouseClicked;
+ _isSelectingAssemblyInstallationPoint = false;
+ _pathPlanningManager?.EnableMouseHandling();
+ _pathPlanningManager?.StopClickTool();
+ OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
+ OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
+ OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint));
+ OnPropertyChanged(nameof(AssemblyInstallationPointButtonText));
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[直线装配] 清理参考安装点拾取状态失败: {ex.Message}", ex);
+ }
+ }
+
private void CleanupAssemblyEndFaceSelection(bool clearVisuals)
{
try
@@ -2149,9 +2515,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
_assemblyEndFaceSeedPoints.Clear();
ClearAssemblyEndFaceAnalysisVisuals();
+ ResetAssemblyEndFaceAnalysisState();
}
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
+ OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint));
+ OnPropertyChanged(nameof(AssemblyInstallationPointButtonText));
}
catch (Exception ex)
{
@@ -2208,9 +2577,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
RenderAssemblyEndFaceNormal(centerPoint, result.Normal);
RefreshAssemblyTerminalObjectInfo();
RefreshAssemblyReferenceRodIfNeeded();
-
- AssemblyTerminalObjectInfo =
- $"{AssemblyTerminalObjectInfo} | 端面中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2}),候选三角形={result.CandidateTriangleCount},偏差={result.MaxPlaneDeviation:F6}";
UpdateMainStatus($"端面分析完成:中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2}),候选三角形={result.CandidateTriangleCount}");
LogManager.Info(
$"[直线装配] 端面分析完成: 中心=({centerPoint.X:F3}, {centerPoint.Y:F3}, {centerPoint.Z:F3}), " +
diff --git a/src/UI/WPF/Views/PathEditingView.xaml b/src/UI/WPF/Views/PathEditingView.xaml
index 0cea737..f71468e 100644
--- a/src/UI/WPF/Views/PathEditingView.xaml
+++ b/src/UI/WPF/Views/PathEditingView.xaml
@@ -263,6 +263,117 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -300,211 +411,6 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
Foreground="#FF2B579A"
Margin="0,5,0,0"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Utils/Assembly/AssemblyInstallationReferenceBuilder.cs b/src/Utils/Assembly/AssemblyInstallationReferenceBuilder.cs
new file mode 100644
index 0000000..2167c93
--- /dev/null
+++ b/src/Utils/Assembly/AssemblyInstallationReferenceBuilder.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Numerics;
+
+namespace NavisworksTransport.Utils.GeometryAnalysis
+{
+ public sealed class AssemblyInstallationReferenceResult
+ {
+ public Vector3 OpticalAxisBasePoint { get; set; }
+ public Vector3 OpticalAxisDirection { get; set; }
+ public Vector3 PickPoint { get; set; }
+ public Vector3 PlaneNormal { get; set; }
+ public Vector3 PlaneSpanDirection { get; set; }
+ public float OffsetDistance { get; set; }
+ public Vector3 InstallLineBasePoint { get; set; }
+ public Vector3 AnchorPoint { get; set; }
+ }
+
+ public static class AssemblyInstallationReferenceBuilder
+ {
+ private const float MinimumOffsetDistance = 1e-4f;
+
+ public static AssemblyInstallationReferenceResult Build(
+ Vector3 opticalAxisBasePoint,
+ Vector3 opticalAxisDirection,
+ Vector3 pickPoint)
+ {
+ float axisLength = opticalAxisDirection.Length();
+ if (axisLength < 1e-6f)
+ {
+ throw new InvalidOperationException("光轴方向长度过小,无法计算安装参考。");
+ }
+
+ Vector3 axisDir = opticalAxisDirection / axisLength;
+ Vector3 axisProjection = ProjectPointToLine(pickPoint, opticalAxisBasePoint, axisDir);
+ Vector3 rawOffset = pickPoint - axisProjection;
+ float offsetDistance = rawOffset.Length();
+ if (offsetDistance < MinimumOffsetDistance)
+ {
+ throw new InvalidOperationException("参考安装点距离光轴过小,无法确定安装参考面,请重新选择。");
+ }
+
+ Vector3 planeNormal = rawOffset / offsetDistance;
+ Vector3 planeSpanDirection = Vector3.Cross(axisDir, planeNormal);
+ float spanLength = planeSpanDirection.Length();
+ if (spanLength < 1e-6f)
+ {
+ throw new InvalidOperationException("安装参考面横向方向计算失败,请重新选择参考安装点。");
+ }
+
+ planeSpanDirection /= spanLength;
+
+ Vector3 installLineBasePoint = opticalAxisBasePoint + planeNormal * offsetDistance;
+ Vector3 anchorPoint = ProjectPointToLine(pickPoint, installLineBasePoint, axisDir);
+
+ return new AssemblyInstallationReferenceResult
+ {
+ OpticalAxisBasePoint = opticalAxisBasePoint,
+ OpticalAxisDirection = axisDir,
+ PickPoint = pickPoint,
+ PlaneNormal = planeNormal,
+ PlaneSpanDirection = planeSpanDirection,
+ OffsetDistance = offsetDistance,
+ InstallLineBasePoint = installLineBasePoint,
+ AnchorPoint = anchorPoint
+ };
+ }
+
+ private static Vector3 ProjectPointToLine(Vector3 point, Vector3 linePoint, Vector3 lineDirection)
+ {
+ Vector3 pointVector = point - linePoint;
+ float projectionLength = Vector3.Dot(pointVector, lineDirection);
+ return linePoint + lineDirection * projectionLength;
+ }
+ }
+}
diff --git a/src/Utils/CoordinateSystem/CanonicalRailPoseBuilder.cs b/src/Utils/CoordinateSystem/CanonicalRailPoseBuilder.cs
index 405f01f..ee77355 100644
--- a/src/Utils/CoordinateSystem/CanonicalRailPoseBuilder.cs
+++ b/src/Utils/CoordinateSystem/CanonicalRailPoseBuilder.cs
@@ -104,5 +104,6 @@ namespace NavisworksTransport.Utils.CoordinateSystem
rotation = convention.CreateQuaternion(forward, up);
return true;
}
+
}
}