From 93ad55c725a928ccd2f115052a25d2ccd13d7bfb Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sun, 22 Mar 2026 23:58:11 +0800 Subject: [PATCH] Refine assembly installation workflow UI --- NavisworksTransport.UnitTests.csproj | 1 + TransportPlugin.csproj | 1 + ...semblyInstallationReferenceBuilderTests.cs | 43 ++ src/Core/PathPointRenderPlugin.cs | 85 ++++ src/UI/WPF/ViewModels/PathEditingViewModel.cs | 402 +++++++++++++++++- src/UI/WPF/Views/PathEditingView.xaml | 316 +++++--------- .../AssemblyInstallationReferenceBuilder.cs | 75 ++++ .../CanonicalRailPoseBuilder.cs | 1 + 8 files changed, 701 insertions(+), 223 deletions(-) create mode 100644 UnitTests/CoordinateSystem/AssemblyInstallationReferenceBuilderTests.cs create mode 100644 src/Utils/Assembly/AssemblyInstallationReferenceBuilder.cs 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 路径编辑页签视图 - 采用与动画控制和分层管 + + + + + + +