Add assembly end-face guided optical axis
This commit is contained in:
parent
b1d4170334
commit
70aaff10bf
@ -61,6 +61,7 @@
|
|||||||
<Compile Include="UnitTests\CoordinateSystem\HoistingCoordinateHelperTests.cs" />
|
<Compile Include="UnitTests\CoordinateSystem\HoistingCoordinateHelperTests.cs" />
|
||||||
<Compile Include="UnitTests\CoordinateSystem\ModelAxisConventionTests.cs" />
|
<Compile Include="UnitTests\CoordinateSystem\ModelAxisConventionTests.cs" />
|
||||||
<Compile Include="UnitTests\CoordinateSystem\ProjectReferenceFrameTests.cs" />
|
<Compile Include="UnitTests\CoordinateSystem\ProjectReferenceFrameTests.cs" />
|
||||||
|
<Compile Include="UnitTests\CoordinateSystem\AssemblyEndFaceAnalyzerTests.cs" />
|
||||||
<Compile Include="UnitTests\Properties\AssemblyInfo.cs" />
|
<Compile Include="UnitTests\Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -139,6 +139,7 @@
|
|||||||
<Compile Include="src\Core\VirtualObjectManager.cs" />
|
<Compile Include="src\Core\VirtualObjectManager.cs" />
|
||||||
<!-- Core - Section Box Export -->
|
<!-- Core - Section Box Export -->
|
||||||
<Compile Include="src\Core\SectionBoxExporter.cs" />
|
<Compile Include="src\Core\SectionBoxExporter.cs" />
|
||||||
|
<Compile Include="src\Utils\Assembly\AssemblyEndFaceAnalyzer.cs" />
|
||||||
<!-- Core - Configuration Management -->
|
<!-- Core - Configuration Management -->
|
||||||
<Compile Include="src\Core\Config\SystemConfig.cs" />
|
<Compile Include="src\Core\Config\SystemConfig.cs" />
|
||||||
<Compile Include="src\Core\Config\ConfigManager.cs" />
|
<Compile Include="src\Core\Config\ConfigManager.cs" />
|
||||||
|
|||||||
113
UnitTests/CoordinateSystem/AssemblyEndFaceAnalyzerTests.cs
Normal file
113
UnitTests/CoordinateSystem/AssemblyEndFaceAnalyzerTests.cs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using NavisworksTransport.Utils.GeometryAnalysis;
|
||||||
|
|
||||||
|
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class AssemblyEndFaceAnalyzerTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Analyze_RectangularFaceWithSideNoise_ShouldReturnFaceCenter()
|
||||||
|
{
|
||||||
|
var triangles = new List<AnalysisTriangle3>();
|
||||||
|
triangles.AddRange(CreateRectangleFace(-2.0, 2.0, -1.0, 1.0, 10.0));
|
||||||
|
triangles.AddRange(CreateRectangleFace(-2.0, 2.0, -1.0, 1.0, 8.0));
|
||||||
|
triangles.Add(new AnalysisTriangle3(
|
||||||
|
new Vector3(-2.0f, -1.0f, 10.0f),
|
||||||
|
new Vector3(-2.0f, -1.0f, 9.0f),
|
||||||
|
new Vector3(-2.0f, 1.0f, 9.0f)));
|
||||||
|
|
||||||
|
EndFaceAnalysisResult result = AssemblyEndFaceAnalyzer.Analyze(
|
||||||
|
triangles,
|
||||||
|
new Vector3(-1.5f, -0.7f, 10.0f),
|
||||||
|
new Vector3(1.4f, -0.5f, 10.0f),
|
||||||
|
new Vector3(1.1f, 0.8f, 10.0f));
|
||||||
|
|
||||||
|
Assert.IsTrue(result.IsReliable, result.DiagnosticMessage);
|
||||||
|
AssertPoint(result.Center, 0.0, 0.0, 10.0);
|
||||||
|
Assert.AreEqual(2, result.CandidateTriangleCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Analyze_SquareRingFace_ShouldReturnRingCenter()
|
||||||
|
{
|
||||||
|
var triangles = new List<AnalysisTriangle3>();
|
||||||
|
triangles.AddRange(CreateRingFace(4.0, 2.0, 5.0));
|
||||||
|
|
||||||
|
EndFaceAnalysisResult result = AssemblyEndFaceAnalyzer.Analyze(
|
||||||
|
triangles,
|
||||||
|
new Vector3(-3.5f, -3.5f, 5.0f),
|
||||||
|
new Vector3(3.5f, -3.5f, 5.0f),
|
||||||
|
new Vector3(3.5f, 3.5f, 5.0f));
|
||||||
|
|
||||||
|
Assert.IsTrue(result.IsReliable, result.DiagnosticMessage);
|
||||||
|
AssertPoint(result.Center, 0.0, 0.0, 5.0);
|
||||||
|
Assert.IsTrue(result.CandidateTriangleCount >= 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Analyze_NearlyCollinearSeedPoints_ShouldFail()
|
||||||
|
{
|
||||||
|
var triangles = new List<AnalysisTriangle3>();
|
||||||
|
triangles.AddRange(CreateRectangleFace(-1.0, 1.0, -1.0, 1.0, 0.0));
|
||||||
|
|
||||||
|
EndFaceAnalysisResult result = AssemblyEndFaceAnalyzer.Analyze(
|
||||||
|
triangles,
|
||||||
|
new Vector3(0.0f, 0.0f, 0.0f),
|
||||||
|
new Vector3(1.0f, 0.0f, 0.0f),
|
||||||
|
new Vector3(2.0f, 0.0f, 0.0f));
|
||||||
|
|
||||||
|
Assert.IsFalse(result.IsReliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<AnalysisTriangle3> CreateRectangleFace(double minX, double maxX, double minY, double maxY, double z)
|
||||||
|
{
|
||||||
|
yield return new AnalysisTriangle3(
|
||||||
|
new Vector3((float)minX, (float)minY, (float)z),
|
||||||
|
new Vector3((float)maxX, (float)minY, (float)z),
|
||||||
|
new Vector3((float)maxX, (float)maxY, (float)z));
|
||||||
|
yield return new AnalysisTriangle3(
|
||||||
|
new Vector3((float)minX, (float)minY, (float)z),
|
||||||
|
new Vector3((float)maxX, (float)maxY, (float)z),
|
||||||
|
new Vector3((float)minX, (float)maxY, (float)z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<AnalysisTriangle3> CreateRingFace(double outerHalfSize, double innerHalfSize, double z)
|
||||||
|
{
|
||||||
|
if (innerHalfSize >= outerHalfSize)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(innerHalfSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (AnalysisTriangle3 triangle in CreateRectangleFace(-outerHalfSize, outerHalfSize, innerHalfSize, outerHalfSize, z))
|
||||||
|
{
|
||||||
|
yield return triangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (AnalysisTriangle3 triangle in CreateRectangleFace(-outerHalfSize, outerHalfSize, -outerHalfSize, -innerHalfSize, z))
|
||||||
|
{
|
||||||
|
yield return triangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (AnalysisTriangle3 triangle in CreateRectangleFace(-outerHalfSize, -innerHalfSize, -innerHalfSize, innerHalfSize, z))
|
||||||
|
{
|
||||||
|
yield return triangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (AnalysisTriangle3 triangle in CreateRectangleFace(innerHalfSize, outerHalfSize, -innerHalfSize, innerHalfSize, z))
|
||||||
|
{
|
||||||
|
yield return triangle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertPoint(Vector3 actual, double x, double y, double z)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(x, actual.X, 1e-5);
|
||||||
|
Assert.AreEqual(y, actual.Y, 1e-5);
|
||||||
|
Assert.AreEqual(z, actual.Z, 1e-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ using System.Windows.Input;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Autodesk.Navisworks.Api;
|
using Autodesk.Navisworks.Api;
|
||||||
using Autodesk.Navisworks.Api.Plugins;
|
using Autodesk.Navisworks.Api.Plugins;
|
||||||
@ -19,6 +20,7 @@ using NavisworksTransport.UI.WPF.Views;
|
|||||||
using NavisworksTransport.UI.WPF.Models;
|
using NavisworksTransport.UI.WPF.Models;
|
||||||
using NavisworksTransport.Utils;
|
using NavisworksTransport.Utils;
|
||||||
using NavisworksTransport.Utils.CoordinateSystem;
|
using NavisworksTransport.Utils.CoordinateSystem;
|
||||||
|
using NavisworksTransport.Utils.GeometryAnalysis;
|
||||||
using NavisworksTransport;
|
using NavisworksTransport;
|
||||||
|
|
||||||
namespace NavisworksTransport.UI.WPF.ViewModels
|
namespace NavisworksTransport.UI.WPF.ViewModels
|
||||||
@ -138,11 +140,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
private RailMountMode _assemblyMountMode = RailMountMode.UnderRail;
|
private RailMountMode _assemblyMountMode = RailMountMode.UnderRail;
|
||||||
private bool _hasAssemblyTerminalObject;
|
private bool _hasAssemblyTerminalObject;
|
||||||
private bool _isSelectingAssemblyStartPoint;
|
private bool _isSelectingAssemblyStartPoint;
|
||||||
|
private bool _isSelectingAssemblyEndFacePoints;
|
||||||
|
private bool _hasAssemblyEndFaceAnalysis;
|
||||||
private ModelItem _assemblyTerminalObject;
|
private ModelItem _assemblyTerminalObject;
|
||||||
private Point3D _assemblyStartPoint;
|
private Point3D _assemblyStartPoint;
|
||||||
|
private Point3D _assemblyEndFaceCenterPoint;
|
||||||
|
private Vector3 _assemblyEndFaceNormal;
|
||||||
|
private readonly List<Point3D> _assemblyEndFaceSeedPoints = new List<Point3D>();
|
||||||
private const string AssemblyAnchorMarkerPathId = "assembly_anchor_marker";
|
private const string AssemblyAnchorMarkerPathId = "assembly_anchor_marker";
|
||||||
private const string AssemblyCenterGuideLinePathId = "assembly_center_guide_line";
|
private const string AssemblyCenterGuideLinePathId = "assembly_center_guide_line";
|
||||||
private const string AssemblyReferenceLinePathId = "assembly_reference_line";
|
private const string AssemblyReferenceLinePathId = "assembly_reference_line";
|
||||||
|
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";
|
||||||
|
|
||||||
// 自动路径起点和终点的路径对象引用(用于正确的ID管理)
|
// 自动路径起点和终点的路径对象引用(用于正确的ID管理)
|
||||||
private PathRoute _autoPathStartPointRoute = null;
|
private PathRoute _autoPathStartPointRoute = null;
|
||||||
@ -960,6 +970,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
public ICommand GenerateAssemblyReferenceRodCommand { get; private set; }
|
public ICommand GenerateAssemblyReferenceRodCommand { get; private set; }
|
||||||
public ICommand SelectAssemblyStartPointCommand { get; private set; }
|
public ICommand SelectAssemblyStartPointCommand { get; private set; }
|
||||||
public ICommand ClearAssemblyReferenceRodCommand { get; private set; }
|
public ICommand ClearAssemblyReferenceRodCommand { get; private set; }
|
||||||
|
public ICommand AnalyzeAssemblyTerminalFaceCommand { get; private set; }
|
||||||
|
|
||||||
// 多层吊装命令
|
// 多层吊装命令
|
||||||
public ICommand SelectMultiLevelStartPointCommand { get; private set; }
|
public ICommand SelectMultiLevelStartPointCommand { get; private set; }
|
||||||
@ -1002,6 +1013,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
_pathPlanningManager != null &&
|
_pathPlanningManager != null &&
|
||||||
AssemblyReferencePathManager.Instance.HasReferenceLine &&
|
AssemblyReferencePathManager.Instance.HasReferenceLine &&
|
||||||
!IsSelectingAssemblyStartPoint;
|
!IsSelectingAssemblyStartPoint;
|
||||||
|
public bool CanAnalyzeAssemblyTerminalFace => HasAssemblyTerminalObject &&
|
||||||
|
_pathPlanningManager != null &&
|
||||||
|
!IsSelectingAssemblyStartPoint &&
|
||||||
|
!IsSelectingAssemblyEndFacePoints;
|
||||||
|
|
||||||
|
public bool IsSelectingAssemblyEndFacePoints => _isSelectingAssemblyEndFacePoints;
|
||||||
|
|
||||||
public bool CanExecuteModifyPoint => SelectedPathPoint != null &&
|
public bool CanExecuteModifyPoint => SelectedPathPoint != null &&
|
||||||
_pathPlanningManager?.PathEditState != PathEditState.EditingPoint;
|
_pathPlanningManager?.PathEditState != PathEditState.EditingPoint;
|
||||||
@ -1285,6 +1302,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
GenerateAssemblyReferenceRodCommand = new RelayCommand(async () => await ExecuteGenerateAssemblyReferenceRodAsync(), () => CanGenerateAssemblyReferenceRod);
|
GenerateAssemblyReferenceRodCommand = new RelayCommand(async () => await ExecuteGenerateAssemblyReferenceRodAsync(), () => CanGenerateAssemblyReferenceRod);
|
||||||
SelectAssemblyStartPointCommand = new RelayCommand(async () => await ExecuteSelectAssemblyStartPointAsync(), () => CanSelectAssemblyStartPoint);
|
SelectAssemblyStartPointCommand = new RelayCommand(async () => await ExecuteSelectAssemblyStartPointAsync(), () => CanSelectAssemblyStartPoint);
|
||||||
ClearAssemblyReferenceRodCommand = new RelayCommand(() => ExecuteClearAssemblyReferenceRod());
|
ClearAssemblyReferenceRodCommand = new RelayCommand(() => ExecuteClearAssemblyReferenceRod());
|
||||||
|
AnalyzeAssemblyTerminalFaceCommand = new RelayCommand(async () => await ExecuteAnalyzeAssemblyTerminalFaceAsync(), () => CanAnalyzeAssemblyTerminalFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -1317,8 +1335,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
InitializeAssemblyAnchorVerticalOffsetFromTerminalObject();
|
InitializeAssemblyAnchorVerticalOffsetFromTerminalObject();
|
||||||
RefreshAssemblyTerminalObjectInfo();
|
RefreshAssemblyTerminalObjectInfo();
|
||||||
RenderAssemblyAnchorMarker();
|
RenderAssemblyAnchorMarker();
|
||||||
|
ClearAssemblyEndFaceAnalysisVisuals();
|
||||||
OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod));
|
OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod));
|
||||||
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
|
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
|
||||||
|
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
|
||||||
UpdateMainStatus($"已捕获终点箱体: {AssemblyTerminalObjectName}");
|
UpdateMainStatus($"已捕获终点箱体: {AssemblyTerminalObjectName}");
|
||||||
LogManager.Info($"[直线装配] 已捕获终点箱体: {AssemblyTerminalObjectName}");
|
LogManager.Info($"[直线装配] 已捕获终点箱体: {AssemblyTerminalObjectName}");
|
||||||
}, "捕获终点箱体");
|
}, "捕获终点箱体");
|
||||||
@ -1414,6 +1434,44 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteAnalyzeAssemblyTerminalFaceAsync()
|
||||||
|
{
|
||||||
|
await SafeExecuteAsync(() =>
|
||||||
|
{
|
||||||
|
if (_pathPlanningManager == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("路径规划管理器未初始化,无法分析端面。");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("终点箱体未设置或已失效,请先捕获终点箱体。");
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanupAssemblyReferenceSelection();
|
||||||
|
CleanupAssemblyEndFaceSelection(clearVisuals: false);
|
||||||
|
ClearAssemblyEndFaceAnalysisVisuals();
|
||||||
|
_assemblyEndFaceSeedPoints.Clear();
|
||||||
|
|
||||||
|
_pathPlanningManager.DisableMouseHandling();
|
||||||
|
_isSelectingAssemblyEndFacePoints = true;
|
||||||
|
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
|
||||||
|
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
|
||||||
|
|
||||||
|
PathClickToolPlugin.MouseClicked -= OnAssemblyEndFaceMouseClicked;
|
||||||
|
PathClickToolPlugin.MouseClicked += OnAssemblyEndFaceMouseClicked;
|
||||||
|
|
||||||
|
if (!ForceReinitializeToolPlugin(subscribeToEvents: false))
|
||||||
|
{
|
||||||
|
CleanupAssemblyEndFaceSelection(clearVisuals: false);
|
||||||
|
throw new InvalidOperationException("ToolPlugin 初始化失败,请重试。");
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateMainStatus("请在同一个端面平面上连续点击 3 个点,系统将分析端面中心。");
|
||||||
|
LogManager.Info("[直线装配] 已进入端面三点分析模式");
|
||||||
|
}, "分析终端端面");
|
||||||
|
}
|
||||||
|
|
||||||
private async void OnAssemblyReferenceMouseClicked(object sender, PickItemResult pickResult)
|
private async void OnAssemblyReferenceMouseClicked(object sender, PickItemResult pickResult)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -1445,6 +1503,47 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void OnAssemblyEndFaceMouseClicked(object sender, PickItemResult pickResult)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_isSelectingAssemblyEndFacePoints || pickResult == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SafeExecuteAsync(() =>
|
||||||
|
{
|
||||||
|
if (!IsPickOnAssemblyTerminalObject(pickResult))
|
||||||
|
{
|
||||||
|
UpdateMainStatus("请点击当前终点箱体的同一个端面,不要点到其他对象。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_assemblyEndFaceSeedPoints.Add(pickResult.Point);
|
||||||
|
RenderAssemblyEndFaceSeedPoints();
|
||||||
|
|
||||||
|
int pickedCount = _assemblyEndFaceSeedPoints.Count;
|
||||||
|
LogManager.Info($"[直线装配] 已记录端面种子点 {pickedCount}: ({pickResult.Point.X:F3}, {pickResult.Point.Y:F3}, {pickResult.Point.Z:F3})");
|
||||||
|
|
||||||
|
if (pickedCount < 3)
|
||||||
|
{
|
||||||
|
UpdateMainStatus($"已记录端面点 {pickedCount}/3,请继续在同一端面平面上点击。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalyzeCurrentAssemblyEndFace();
|
||||||
|
CleanupAssemblyEndFaceSelection(clearVisuals: false);
|
||||||
|
}, "处理端面三点拾取");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogManager.Error($"[直线装配] 端面三点分析失败: {ex.Message}", ex);
|
||||||
|
UpdateMainStatus($"端面三点分析失败: {ex.Message}");
|
||||||
|
CleanupAssemblyEndFaceSelection(clearVisuals: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateAssemblyLinearRoute(Point3D startPoint)
|
private void CreateAssemblyLinearRoute(Point3D startPoint)
|
||||||
{
|
{
|
||||||
Point3D endPoint = AssemblyReferencePathManager.Instance.ReferenceLineEnd;
|
Point3D endPoint = AssemblyReferencePathManager.Instance.ReferenceLineEnd;
|
||||||
@ -1505,15 +1604,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
private void HideAssemblyReferenceVisuals(bool resetStartPointText, string statusMessage, string logMessage)
|
private void HideAssemblyReferenceVisuals(bool resetStartPointText, string statusMessage, string logMessage)
|
||||||
{
|
{
|
||||||
CleanupAssemblyReferenceSelection();
|
CleanupAssemblyReferenceSelection();
|
||||||
|
CleanupAssemblyEndFaceSelection(clearVisuals: false);
|
||||||
AssemblyReferencePathManager.Instance.HideReferenceRod();
|
AssemblyReferencePathManager.Instance.HideReferenceRod();
|
||||||
ClearAssemblyAnchorMarker();
|
ClearAssemblyAnchorMarker();
|
||||||
ClearAssemblyCenterGuideLine();
|
ClearAssemblyCenterGuideLine();
|
||||||
ClearAssemblyReferenceLine();
|
ClearAssemblyReferenceLine();
|
||||||
|
ClearAssemblyEndFaceAnalysisVisuals();
|
||||||
if (resetStartPointText)
|
if (resetStartPointText)
|
||||||
{
|
{
|
||||||
AssemblyStartPointText = "未选择";
|
AssemblyStartPointText = "未选择";
|
||||||
}
|
}
|
||||||
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
|
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
|
||||||
|
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
|
||||||
if (!string.IsNullOrWhiteSpace(statusMessage))
|
if (!string.IsNullOrWhiteSpace(statusMessage))
|
||||||
{
|
{
|
||||||
UpdateMainStatus(statusMessage);
|
UpdateMainStatus(statusMessage);
|
||||||
@ -1552,6 +1654,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
return adapter.FromCanonicalPoint(canonicalAnchorPoint);
|
return adapter.FromCanonicalPoint(canonicalAnchorPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Point3D GetAssemblyOpticalAxisReferencePoint()
|
||||||
|
{
|
||||||
|
if (_hasAssemblyEndFaceAnalysis && _assemblyEndFaceCenterPoint != null)
|
||||||
|
{
|
||||||
|
return _assemblyEndFaceCenterPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("终点箱体未设置或已失效,无法计算光轴参考点");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _assemblyTerminalObject.BoundingBox().Center;
|
||||||
|
}
|
||||||
|
|
||||||
private AssemblyReferenceLine BuildAssemblyReferenceLine()
|
private AssemblyReferenceLine BuildAssemblyReferenceLine()
|
||||||
{
|
{
|
||||||
if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
|
if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
|
||||||
@ -1562,20 +1679,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||||
ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter);
|
ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter);
|
||||||
|
|
||||||
BoundingBox3D bounds = _assemblyTerminalObject.BoundingBox();
|
Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint();
|
||||||
Point3D centerPoint = bounds.Center;
|
|
||||||
Point3D endPoint = GetAssemblyTerminalAnchorPoint();
|
Point3D endPoint = GetAssemblyTerminalAnchorPoint();
|
||||||
|
|
||||||
Point3D canonicalCenterPoint = adapter.ToCanonicalPoint(centerPoint);
|
Point3D canonicalAxisReferencePoint = adapter.ToCanonicalPoint(opticalAxisReferencePoint);
|
||||||
Point3D canonicalEndPoint = adapter.ToCanonicalPoint(endPoint);
|
Point3D canonicalEndPoint = adapter.ToCanonicalPoint(endPoint);
|
||||||
Vector3D direction = new Vector3D(
|
Vector3D direction = new Vector3D(
|
||||||
canonicalCenterPoint.X - projectFrame.SphereCenterInCanonical.X,
|
canonicalAxisReferencePoint.X - projectFrame.SphereCenterInCanonical.X,
|
||||||
canonicalCenterPoint.Y - projectFrame.SphereCenterInCanonical.Y,
|
canonicalAxisReferencePoint.Y - projectFrame.SphereCenterInCanonical.Y,
|
||||||
canonicalCenterPoint.Z - projectFrame.SphereCenterInCanonical.Z);
|
canonicalAxisReferencePoint.Z - projectFrame.SphereCenterInCanonical.Z);
|
||||||
double directionLengthSquared = direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z;
|
double directionLengthSquared = direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z;
|
||||||
if (directionLengthSquared < 1e-9)
|
if (directionLengthSquared < 1e-9)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("箱体中心与项目球心重合,无法生成装配参考线方向");
|
throw new InvalidOperationException("光轴参考点与项目球心重合,无法生成装配参考线方向");
|
||||||
}
|
}
|
||||||
|
|
||||||
direction = direction.Normalize();
|
direction = direction.Normalize();
|
||||||
@ -1588,9 +1704,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
|
|
||||||
LogManager.Info(
|
LogManager.Info(
|
||||||
$"[直线装配] 参考线已计算,终点锚点=({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2}), " +
|
$"[直线装配] 参考线已计算,终点锚点=({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2}), " +
|
||||||
$"箱体中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2}), " +
|
$"光轴参考点=({opticalAxisReferencePoint.X:F2}, {opticalAxisReferencePoint.Y:F2}, {opticalAxisReferencePoint.Z:F2}), " +
|
||||||
$"参考线外端=({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2}), " +
|
$"参考线外端=({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2}), " +
|
||||||
$"球心到箱体中心方向(内部坐标)=({direction.X:F3}, {direction.Y:F3}, {direction.Z:F3})");
|
$"球心到光轴参考点方向(内部坐标)=({direction.X:F3}, {direction.Y:F3}, {direction.Z:F3})");
|
||||||
|
|
||||||
return new AssemblyReferenceLine(startPoint, endPoint, direction);
|
return new AssemblyReferenceLine(startPoint, endPoint, direction);
|
||||||
}
|
}
|
||||||
@ -1606,11 +1722,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
Point3D anchorPoint = GetAssemblyTerminalAnchorPoint();
|
Point3D anchorPoint = GetAssemblyTerminalAnchorPoint();
|
||||||
string anchorText = GetAssemblyAnchorText();
|
string anchorText = GetAssemblyAnchorText();
|
||||||
string mountText = AssemblyMountMode == RailMountMode.OverRail ? "轨上安装" : "轨下安装";
|
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);
|
||||||
|
|
||||||
if (referenceStartPoint != null && referenceEndPoint != null)
|
if (referenceStartPoint != null && referenceEndPoint != null)
|
||||||
{
|
{
|
||||||
AssemblyTerminalObjectInfo = string.Format(
|
AssemblyTerminalObjectInfo = string.Format(
|
||||||
"中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7}点=({8:F2}, {9:F2}, {10:F2}),垂直偏移={11:F3}m,辅助线外端=({12:F2}, {13:F2}, {14:F2})",
|
"中心=({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})",
|
||||||
bounds.Center.X,
|
bounds.Center.X,
|
||||||
bounds.Center.Y,
|
bounds.Center.Y,
|
||||||
bounds.Center.Z,
|
bounds.Center.Z,
|
||||||
@ -1622,6 +1742,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
referenceEndPoint.X,
|
referenceEndPoint.X,
|
||||||
referenceEndPoint.Y,
|
referenceEndPoint.Y,
|
||||||
referenceEndPoint.Z,
|
referenceEndPoint.Z,
|
||||||
|
opticalAxisText,
|
||||||
AssemblyAnchorVerticalOffsetInMeters,
|
AssemblyAnchorVerticalOffsetInMeters,
|
||||||
referenceStartPoint.X,
|
referenceStartPoint.X,
|
||||||
referenceStartPoint.Y,
|
referenceStartPoint.Y,
|
||||||
@ -1630,7 +1751,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
AssemblyTerminalObjectInfo = string.Format(
|
AssemblyTerminalObjectInfo = string.Format(
|
||||||
"中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7}点=({8:F2}, {9:F2}, {10:F2}),垂直偏移={11:F3}m",
|
"中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7}点=({8:F2}, {9:F2}, {10:F2}),{11},垂直偏移={12:F3}m",
|
||||||
bounds.Center.X,
|
bounds.Center.X,
|
||||||
bounds.Center.Y,
|
bounds.Center.Y,
|
||||||
bounds.Center.Z,
|
bounds.Center.Z,
|
||||||
@ -1642,6 +1763,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
anchorPoint.X,
|
anchorPoint.X,
|
||||||
anchorPoint.Y,
|
anchorPoint.Y,
|
||||||
anchorPoint.Z,
|
anchorPoint.Z,
|
||||||
|
opticalAxisText,
|
||||||
AssemblyAnchorVerticalOffsetInMeters);
|
AssemblyAnchorVerticalOffsetInMeters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1800,7 +1922,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
Id = AssemblyAnchorMarkerPathId,
|
Id = AssemblyAnchorMarkerPathId,
|
||||||
Description = "直线装配终点对接标记"
|
Description = "直线装配终点对接标记"
|
||||||
};
|
};
|
||||||
markerRoute.AddPoint(new PathPoint(anchorPoint, "对接点", PathPointType.EndPoint));
|
AddVisualizationPoint(markerRoute, anchorPoint, "对接点", PathPointType.EndPoint);
|
||||||
renderPlugin.RenderPointOnly(markerRoute);
|
renderPlugin.RenderPointOnly(markerRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1837,7 +1959,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||||
ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter);
|
ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter);
|
||||||
|
|
||||||
Point3D centerPoint = _assemblyTerminalObject.BoundingBox().Center;
|
Point3D centerPoint = GetAssemblyOpticalAxisReferencePoint();
|
||||||
Point3D sphereCenterPoint = adapter.FromCanonicalPoint(projectFrame.SphereCenterInCanonical);
|
Point3D sphereCenterPoint = adapter.FromCanonicalPoint(projectFrame.SphereCenterInCanonical);
|
||||||
renderPlugin.RenderRailBaseline(
|
renderPlugin.RenderRailBaseline(
|
||||||
AssemblyCenterGuideLinePathId,
|
AssemblyCenterGuideLinePathId,
|
||||||
@ -1845,8 +1967,90 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
RenderStyleName.AssemblyGuideLine);
|
RenderStyleName.AssemblyGuideLine);
|
||||||
|
|
||||||
LogManager.Info(
|
LogManager.Info(
|
||||||
$"[直线装配] 已渲染球心到箱体中心基准线: 球心=({sphereCenterPoint.X:F2}, {sphereCenterPoint.Y:F2}, {sphereCenterPoint.Z:F2}), " +
|
$"[直线装配] 已渲染球心到光轴参考点基准线: 球心=({sphereCenterPoint.X:F2}, {sphereCenterPoint.Y:F2}, {sphereCenterPoint.Z:F2}), " +
|
||||||
$"箱体中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2})");
|
$"光轴参考点=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2})");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderAssemblyEndFaceSeedPoints()
|
||||||
|
{
|
||||||
|
var renderPlugin = PathPointRenderPlugin.Instance;
|
||||||
|
if (renderPlugin == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlugin.RemovePath(AssemblyEndFaceSeedPathId);
|
||||||
|
|
||||||
|
if (_assemblyEndFaceSeedPoints.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var markerRoute = new PathRoute("端面三点")
|
||||||
|
{
|
||||||
|
Id = AssemblyEndFaceSeedPathId,
|
||||||
|
Description = "终端安装端面三点"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < _assemblyEndFaceSeedPoints.Count; i++)
|
||||||
|
{
|
||||||
|
AddVisualizationPoint(markerRoute, _assemblyEndFaceSeedPoints[i], $"端面点{i + 1}", PathPointType.WayPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlugin.RenderPointOnly(markerRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderAssemblyEndFaceCenter(Point3D centerPoint)
|
||||||
|
{
|
||||||
|
var renderPlugin = PathPointRenderPlugin.Instance;
|
||||||
|
if (renderPlugin == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlugin.RemovePath(AssemblyEndFaceCenterPathId);
|
||||||
|
|
||||||
|
var markerRoute = new PathRoute("端面中心")
|
||||||
|
{
|
||||||
|
Id = AssemblyEndFaceCenterPathId,
|
||||||
|
Description = "终端安装端面中心"
|
||||||
|
};
|
||||||
|
AddVisualizationPoint(markerRoute, centerPoint, "端面中心", PathPointType.EndPoint);
|
||||||
|
renderPlugin.RenderPointOnly(markerRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddVisualizationPoint(PathRoute route, Point3D position, string name, PathPointType type)
|
||||||
|
{
|
||||||
|
if (route == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(route));
|
||||||
|
}
|
||||||
|
|
||||||
|
route.Points.Add(new PathPoint(position, name, type)
|
||||||
|
{
|
||||||
|
Index = route.Points.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderAssemblyEndFaceNormal(Point3D centerPoint, Vector3 normal)
|
||||||
|
{
|
||||||
|
var renderPlugin = PathPointRenderPlugin.Instance;
|
||||||
|
if (renderPlugin == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlugin.ClearRailBaseline(AssemblyEndFaceNormalPathId);
|
||||||
|
|
||||||
|
double lineLength = UnitsConverter.ConvertFromMeters(Math.Max(0.5, AssemblyReferenceRodDiameterInMeters * 4.0));
|
||||||
|
Point3D normalEndPoint = new Point3D(
|
||||||
|
centerPoint.X + normal.X * (float)lineLength,
|
||||||
|
centerPoint.Y + normal.Y * (float)lineLength,
|
||||||
|
centerPoint.Z + normal.Z * (float)lineLength);
|
||||||
|
renderPlugin.RenderRailBaseline(
|
||||||
|
AssemblyEndFaceNormalPathId,
|
||||||
|
new List<Point3D> { centerPoint, normalEndPoint },
|
||||||
|
RenderStyleName.AssemblyGuideLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProjectReferenceFrame CreateAssemblyProjectReferenceFrame(HostCoordinateAdapter adapter)
|
private ProjectReferenceFrame CreateAssemblyProjectReferenceFrame(HostCoordinateAdapter adapter)
|
||||||
@ -1901,6 +2105,22 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
renderPlugin.ClearRailBaseline(AssemblyCenterGuideLinePathId);
|
renderPlugin.ClearRailBaseline(AssemblyCenterGuideLinePathId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearAssemblyEndFaceAnalysisVisuals()
|
||||||
|
{
|
||||||
|
var renderPlugin = PathPointRenderPlugin.Instance;
|
||||||
|
if (renderPlugin == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlugin.RemovePath(AssemblyEndFaceSeedPathId);
|
||||||
|
renderPlugin.RemovePath(AssemblyEndFaceCenterPathId);
|
||||||
|
renderPlugin.ClearRailBaseline(AssemblyEndFaceNormalPathId);
|
||||||
|
_hasAssemblyEndFaceAnalysis = false;
|
||||||
|
_assemblyEndFaceCenterPoint = null;
|
||||||
|
_assemblyEndFaceNormal = default(Vector3);
|
||||||
|
}
|
||||||
|
|
||||||
private void CleanupAssemblyReferenceSelection()
|
private void CleanupAssemblyReferenceSelection()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -1917,6 +2137,87 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CleanupAssemblyEndFaceSelection(bool clearVisuals)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PathClickToolPlugin.MouseClicked -= OnAssemblyEndFaceMouseClicked;
|
||||||
|
_isSelectingAssemblyEndFacePoints = false;
|
||||||
|
_pathPlanningManager?.EnableMouseHandling();
|
||||||
|
_pathPlanningManager?.StopClickTool();
|
||||||
|
if (clearVisuals)
|
||||||
|
{
|
||||||
|
_assemblyEndFaceSeedPoints.Clear();
|
||||||
|
ClearAssemblyEndFaceAnalysisVisuals();
|
||||||
|
}
|
||||||
|
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
|
||||||
|
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogManager.Error($"[直线装配] 清理端面三点拾取状态失败: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPickOnAssemblyTerminalObject(PickItemResult pickResult)
|
||||||
|
{
|
||||||
|
if (pickResult?.ModelItem == null || _assemblyTerminalObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ModelItemAnalysisHelper.ModelItemEquals(pickResult.ModelItem, _assemblyTerminalObject))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pickResult.ModelItem.AncestorsAndSelf.Any(ancestor => ModelItemAnalysisHelper.ModelItemEquals(ancestor, _assemblyTerminalObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AnalyzeCurrentAssemblyEndFace()
|
||||||
|
{
|
||||||
|
if (_assemblyEndFaceSeedPoints.Count != 3)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("端面分析需要恰好 3 个种子点。");
|
||||||
|
}
|
||||||
|
|
||||||
|
var triangles = GeometryHelper.ExtractTriangles(new[] { _assemblyTerminalObject })
|
||||||
|
.Select(triangle => new AnalysisTriangle3(
|
||||||
|
new Vector3((float)triangle.Point1.X, (float)triangle.Point1.Y, (float)triangle.Point1.Z),
|
||||||
|
new Vector3((float)triangle.Point2.X, (float)triangle.Point2.Y, (float)triangle.Point2.Z),
|
||||||
|
new Vector3((float)triangle.Point3.X, (float)triangle.Point3.Y, (float)triangle.Point3.Z)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
EndFaceAnalysisResult result = AssemblyEndFaceAnalyzer.Analyze(
|
||||||
|
triangles,
|
||||||
|
new Vector3((float)_assemblyEndFaceSeedPoints[0].X, (float)_assemblyEndFaceSeedPoints[0].Y, (float)_assemblyEndFaceSeedPoints[0].Z),
|
||||||
|
new Vector3((float)_assemblyEndFaceSeedPoints[1].X, (float)_assemblyEndFaceSeedPoints[1].Y, (float)_assemblyEndFaceSeedPoints[1].Z),
|
||||||
|
new Vector3((float)_assemblyEndFaceSeedPoints[2].X, (float)_assemblyEndFaceSeedPoints[2].Y, (float)_assemblyEndFaceSeedPoints[2].Z));
|
||||||
|
|
||||||
|
if (!result.IsReliable)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(result.DiagnosticMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point3D centerPoint = AssemblyEndFaceAnalyzer.ToPoint3D(result.Center);
|
||||||
|
_hasAssemblyEndFaceAnalysis = true;
|
||||||
|
_assemblyEndFaceCenterPoint = centerPoint;
|
||||||
|
_assemblyEndFaceNormal = result.Normal;
|
||||||
|
RenderAssemblyEndFaceSeedPoints();
|
||||||
|
RenderAssemblyEndFaceCenter(centerPoint);
|
||||||
|
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}), " +
|
||||||
|
$"法向=({result.Normal.X:F4}, {result.Normal.Y:F4}, {result.Normal.Z:F4}), " +
|
||||||
|
$"三角形={result.CandidateTriangleCount}, 顶点={result.CandidateVertexCount}, 偏差={result.MaxPlaneDeviation:F6}");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ExecuteNewPathAsync()
|
private async Task ExecuteNewPathAsync()
|
||||||
{
|
{
|
||||||
await SafeExecuteAsync(() =>
|
await SafeExecuteAsync(() =>
|
||||||
@ -5377,6 +5678,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
LogManager.Warning($"清理直线装配事件订阅时发生异常: {ex.Message}");
|
LogManager.Warning($"清理直线装配事件订阅时发生异常: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CleanupAssemblyEndFaceSelection(clearVisuals: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogManager.Warning($"清理端面三点分析事件订阅时发生异常: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ClearAssemblyAnchorMarker();
|
ClearAssemblyAnchorMarker();
|
||||||
|
|||||||
@ -462,6 +462,10 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
|
|||||||
Command="{Binding SelectAssemblyStartPointCommand}"
|
Command="{Binding SelectAssemblyStartPointCommand}"
|
||||||
Style="{StaticResource ActionButtonStyle}"
|
Style="{StaticResource ActionButtonStyle}"
|
||||||
IsEnabled="{Binding CanSelectAssemblyStartPoint}"/>
|
IsEnabled="{Binding CanSelectAssemblyStartPoint}"/>
|
||||||
|
<Button Content="分析端面(3点)"
|
||||||
|
Command="{Binding AnalyzeAssemblyTerminalFaceCommand}"
|
||||||
|
Style="{StaticResource SecondaryButtonStyle}"
|
||||||
|
IsEnabled="{Binding CanAnalyzeAssemblyTerminalFace}"/>
|
||||||
<Button Content="隐藏辅助线"
|
<Button Content="隐藏辅助线"
|
||||||
Command="{Binding ClearAssemblyReferenceRodCommand}"
|
Command="{Binding ClearAssemblyReferenceRodCommand}"
|
||||||
Style="{StaticResource SecondaryButtonStyle}"/>
|
Style="{StaticResource SecondaryButtonStyle}"/>
|
||||||
|
|||||||
342
src/Utils/Assembly/AssemblyEndFaceAnalyzer.cs
Normal file
342
src/Utils/Assembly/AssemblyEndFaceAnalyzer.cs
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Autodesk.Navisworks.Api;
|
||||||
|
|
||||||
|
namespace NavisworksTransport.Utils.GeometryAnalysis
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 基于端面上的三个种子点,从真实三角几何中识别端面并估计几何中心。
|
||||||
|
/// 算法核心使用纯 Vector3,便于单测独立运行。
|
||||||
|
/// </summary>
|
||||||
|
public static class AssemblyEndFaceAnalyzer
|
||||||
|
{
|
||||||
|
private const double MinimumSeedTriangleAreaSquared = 1e-8;
|
||||||
|
private const double PlaneDistanceTolerance = 1e-3;
|
||||||
|
private const double NormalAlignmentCosineThreshold = 0.98;
|
||||||
|
private const double MinimumProjectedExtent = 1e-6;
|
||||||
|
|
||||||
|
public static EndFaceAnalysisResult Analyze(ModelItem modelItem, Point3D seedPoint1, Point3D seedPoint2, Point3D seedPoint3)
|
||||||
|
{
|
||||||
|
if (modelItem == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(modelItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AnalysisTriangle3> triangles = GeometryHelper.ExtractTriangles(new[] { modelItem })
|
||||||
|
.Select(ConvertTriangle)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Analyze(triangles, ToVector3(seedPoint1), ToVector3(seedPoint2), ToVector3(seedPoint3));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EndFaceAnalysisResult Analyze(
|
||||||
|
IEnumerable<AnalysisTriangle3> triangles,
|
||||||
|
Vector3 seedPoint1,
|
||||||
|
Vector3 seedPoint2,
|
||||||
|
Vector3 seedPoint3)
|
||||||
|
{
|
||||||
|
if (triangles == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(triangles));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AnalysisTriangle3> triangleList = triangles.ToList();
|
||||||
|
if (triangleList.Count == 0)
|
||||||
|
{
|
||||||
|
return EndFaceAnalysisResult.Failure("没有可用的三角几何数据。");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaneDefinition seedPlane;
|
||||||
|
if (!TryCreatePlane(seedPoint1, seedPoint2, seedPoint3, out seedPlane))
|
||||||
|
{
|
||||||
|
return EndFaceAnalysisResult.Failure("三个端面点近似共线,无法拟合端面平面。");
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidateTriangles = new List<AnalysisTriangle3>();
|
||||||
|
var candidatePoints = new HashSet<Vector3>(new Vector3EqualityComparer());
|
||||||
|
double maxPlaneDeviation = 0.0;
|
||||||
|
|
||||||
|
foreach (AnalysisTriangle3 triangle in triangleList)
|
||||||
|
{
|
||||||
|
Vector3 triangleNormal;
|
||||||
|
if (!TryGetTriangleNormal(triangle, out triangleNormal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double normalAlignment = Math.Abs(Vector3.Dot(triangleNormal, seedPlane.Normal));
|
||||||
|
if (normalAlignment < NormalAlignmentCosineThreshold)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double distance1 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point1));
|
||||||
|
double distance2 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point2));
|
||||||
|
double distance3 = Math.Abs(GetSignedDistanceToPlane(seedPlane, triangle.Point3));
|
||||||
|
double triangleDeviation = Math.Max(distance1, Math.Max(distance2, distance3));
|
||||||
|
if (triangleDeviation > PlaneDistanceTolerance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidateTriangles.Add(triangle);
|
||||||
|
candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point1));
|
||||||
|
candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point2));
|
||||||
|
candidatePoints.Add(ProjectPointToPlane(seedPlane, triangle.Point3));
|
||||||
|
maxPlaneDeviation = Math.Max(maxPlaneDeviation, triangleDeviation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidateTriangles.Count == 0 || candidatePoints.Count < 3)
|
||||||
|
{
|
||||||
|
return EndFaceAnalysisResult.Failure("没有识别到足够的端面共面三角形。");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaneDefinition refinedPlane = RefinePlane(seedPlane, candidatePoints);
|
||||||
|
PlaneBasis planeBasis = CreatePlaneBasis(refinedPlane);
|
||||||
|
|
||||||
|
double minU = double.MaxValue;
|
||||||
|
double maxU = double.MinValue;
|
||||||
|
double minV = double.MaxValue;
|
||||||
|
double maxV = double.MinValue;
|
||||||
|
|
||||||
|
foreach (Vector3 candidatePoint in candidatePoints)
|
||||||
|
{
|
||||||
|
Vector2 projected = ProjectToPlane2D(candidatePoint, planeBasis);
|
||||||
|
minU = Math.Min(minU, projected.X);
|
||||||
|
maxU = Math.Max(maxU, projected.X);
|
||||||
|
minV = Math.Min(minV, projected.Y);
|
||||||
|
maxV = Math.Max(maxV, projected.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
double extentU = maxU - minU;
|
||||||
|
double extentV = maxV - minV;
|
||||||
|
if (extentU < MinimumProjectedExtent || extentV < MinimumProjectedExtent)
|
||||||
|
{
|
||||||
|
return EndFaceAnalysisResult.Failure("识别到的端面分布过窄,无法稳定求中心。");
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 center = ProjectFromPlane2D((minU + maxU) / 2.0, (minV + maxV) / 2.0, planeBasis);
|
||||||
|
|
||||||
|
return EndFaceAnalysisResult.Success(
|
||||||
|
center,
|
||||||
|
refinedPlane.Normal,
|
||||||
|
candidateTriangles.Count,
|
||||||
|
candidatePoints.Count,
|
||||||
|
maxPlaneDeviation,
|
||||||
|
$"端面识别完成: 三角形={candidateTriangles.Count}, 顶点={candidatePoints.Count}, 偏差={maxPlaneDeviation:F6}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point3D ToPoint3D(Vector3 point)
|
||||||
|
{
|
||||||
|
return new Point3D(point.X, point.Y, point.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AnalysisTriangle3 ConvertTriangle(Triangle3D triangle)
|
||||||
|
{
|
||||||
|
return new AnalysisTriangle3(ToVector3(triangle.Point1), ToVector3(triangle.Point2), ToVector3(triangle.Point3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 ToVector3(Point3D point)
|
||||||
|
{
|
||||||
|
return new Vector3((float)point.X, (float)point.Y, (float)point.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryCreatePlane(Vector3 point1, Vector3 point2, Vector3 point3, out PlaneDefinition plane)
|
||||||
|
{
|
||||||
|
Vector3 edge1 = point2 - point1;
|
||||||
|
Vector3 edge2 = point3 - point1;
|
||||||
|
Vector3 normal = Vector3.Cross(edge1, edge2);
|
||||||
|
if (normal.LengthSquared() < MinimumSeedTriangleAreaSquared)
|
||||||
|
{
|
||||||
|
plane = default(PlaneDefinition);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 normalizedNormal = Vector3.Normalize(normal);
|
||||||
|
plane = new PlaneDefinition(normalizedNormal, Vector3.Dot(normalizedNormal, point1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetTriangleNormal(AnalysisTriangle3 triangle, out Vector3 normal)
|
||||||
|
{
|
||||||
|
Vector3 edge1 = triangle.Point2 - triangle.Point1;
|
||||||
|
Vector3 edge2 = triangle.Point3 - triangle.Point1;
|
||||||
|
Vector3 cross = Vector3.Cross(edge1, edge2);
|
||||||
|
if (cross.LengthSquared() < 1e-12)
|
||||||
|
{
|
||||||
|
normal = Vector3.Zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
normal = Vector3.Normalize(cross);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlaneDefinition RefinePlane(PlaneDefinition seedPlane, IEnumerable<Vector3> candidatePoints)
|
||||||
|
{
|
||||||
|
double averageOffset = candidatePoints.Average(point => Vector3.Dot(seedPlane.Normal, point));
|
||||||
|
return new PlaneDefinition(seedPlane.Normal, averageOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetSignedDistanceToPlane(PlaneDefinition plane, Vector3 point)
|
||||||
|
{
|
||||||
|
return Vector3.Dot(plane.Normal, point) - plane.Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 ProjectPointToPlane(PlaneDefinition plane, Vector3 point)
|
||||||
|
{
|
||||||
|
float signedDistance = (float)GetSignedDistanceToPlane(plane, point);
|
||||||
|
return point - plane.Normal * signedDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlaneBasis CreatePlaneBasis(PlaneDefinition plane)
|
||||||
|
{
|
||||||
|
Vector3 reference = Math.Abs(plane.Normal.Z) < 0.9f
|
||||||
|
? new Vector3(0.0f, 0.0f, 1.0f)
|
||||||
|
: new Vector3(1.0f, 0.0f, 0.0f);
|
||||||
|
Vector3 axisU = Vector3.Normalize(Vector3.Cross(reference, plane.Normal));
|
||||||
|
Vector3 axisV = Vector3.Normalize(Vector3.Cross(plane.Normal, axisU));
|
||||||
|
Vector3 origin = plane.Normal * (float)plane.Offset;
|
||||||
|
return new PlaneBasis(origin, axisU, axisV);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2 ProjectToPlane2D(Vector3 point, PlaneBasis basis)
|
||||||
|
{
|
||||||
|
Vector3 vector = point - basis.Origin;
|
||||||
|
return new Vector2(Vector3.Dot(vector, basis.AxisU), Vector3.Dot(vector, basis.AxisV));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 ProjectFromPlane2D(double u, double v, PlaneBasis basis)
|
||||||
|
{
|
||||||
|
return basis.Origin + basis.AxisU * (float)u + basis.AxisV * (float)v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PlaneDefinition
|
||||||
|
{
|
||||||
|
public PlaneDefinition(Vector3 normal, double offset)
|
||||||
|
{
|
||||||
|
Normal = normal;
|
||||||
|
Offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Normal { get; }
|
||||||
|
public double Offset { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PlaneBasis
|
||||||
|
{
|
||||||
|
public PlaneBasis(Vector3 origin, Vector3 axisU, Vector3 axisV)
|
||||||
|
{
|
||||||
|
Origin = origin;
|
||||||
|
AxisU = axisU;
|
||||||
|
AxisV = axisV;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Origin { get; }
|
||||||
|
public Vector3 AxisU { get; }
|
||||||
|
public Vector3 AxisV { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Vector3EqualityComparer : IEqualityComparer<Vector3>
|
||||||
|
{
|
||||||
|
private const float Tolerance = 1e-5f;
|
||||||
|
|
||||||
|
public bool Equals(Vector3 x, Vector3 y)
|
||||||
|
{
|
||||||
|
return Math.Abs(x.X - y.X) <= Tolerance &&
|
||||||
|
Math.Abs(x.Y - y.Y) <= Tolerance &&
|
||||||
|
Math.Abs(x.Z - y.Z) <= Tolerance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(Vector3 obj)
|
||||||
|
{
|
||||||
|
int x = (int)Math.Round(obj.X / Tolerance);
|
||||||
|
int y = (int)Math.Round(obj.Y / Tolerance);
|
||||||
|
int z = (int)Math.Round(obj.Z / Tolerance);
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int hash = 17;
|
||||||
|
hash = hash * 31 + x;
|
||||||
|
hash = hash * 31 + y;
|
||||||
|
hash = hash * 31 + z;
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AnalysisTriangle3
|
||||||
|
{
|
||||||
|
public AnalysisTriangle3(Vector3 point1, Vector3 point2, Vector3 point3)
|
||||||
|
{
|
||||||
|
Point1 = point1;
|
||||||
|
Point2 = point2;
|
||||||
|
Point3 = point3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Point1 { get; }
|
||||||
|
public Vector3 Point2 { get; }
|
||||||
|
public Vector3 Point3 { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EndFaceAnalysisResult
|
||||||
|
{
|
||||||
|
private EndFaceAnalysisResult(
|
||||||
|
bool isReliable,
|
||||||
|
Vector3 center,
|
||||||
|
Vector3 normal,
|
||||||
|
int candidateTriangleCount,
|
||||||
|
int candidateVertexCount,
|
||||||
|
double maxPlaneDeviation,
|
||||||
|
string diagnosticMessage)
|
||||||
|
{
|
||||||
|
IsReliable = isReliable;
|
||||||
|
Center = center;
|
||||||
|
Normal = normal;
|
||||||
|
CandidateTriangleCount = candidateTriangleCount;
|
||||||
|
CandidateVertexCount = candidateVertexCount;
|
||||||
|
MaxPlaneDeviation = maxPlaneDeviation;
|
||||||
|
DiagnosticMessage = diagnosticMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReliable { get; }
|
||||||
|
public Vector3 Center { get; }
|
||||||
|
public Vector3 Normal { get; }
|
||||||
|
public int CandidateTriangleCount { get; }
|
||||||
|
public int CandidateVertexCount { get; }
|
||||||
|
public double MaxPlaneDeviation { get; }
|
||||||
|
public string DiagnosticMessage { get; }
|
||||||
|
|
||||||
|
public static EndFaceAnalysisResult Success(
|
||||||
|
Vector3 center,
|
||||||
|
Vector3 normal,
|
||||||
|
int candidateTriangleCount,
|
||||||
|
int candidateVertexCount,
|
||||||
|
double maxPlaneDeviation,
|
||||||
|
string diagnosticMessage)
|
||||||
|
{
|
||||||
|
return new EndFaceAnalysisResult(
|
||||||
|
true,
|
||||||
|
center,
|
||||||
|
normal,
|
||||||
|
candidateTriangleCount,
|
||||||
|
candidateVertexCount,
|
||||||
|
maxPlaneDeviation,
|
||||||
|
diagnosticMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EndFaceAnalysisResult Failure(string diagnosticMessage)
|
||||||
|
{
|
||||||
|
return new EndFaceAnalysisResult(
|
||||||
|
false,
|
||||||
|
Vector3.Zero,
|
||||||
|
Vector3.Zero,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.0,
|
||||||
|
diagnosticMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user