Refine assembly installation workflow UI

This commit is contained in:
tian 2026-03-22 23:58:11 +08:00
parent 70aaff10bf
commit 93ad55c725
8 changed files with 701 additions and 223 deletions

View File

@ -62,6 +62,7 @@
<Compile Include="UnitTests\CoordinateSystem\ModelAxisConventionTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\ProjectReferenceFrameTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\AssemblyEndFaceAnalyzerTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\AssemblyInstallationReferenceBuilderTests.cs" />
<Compile Include="UnitTests\Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -140,6 +140,7 @@
<!-- Core - Section Box Export -->
<Compile Include="src\Core\SectionBoxExporter.cs" />
<Compile Include="src\Utils\Assembly\AssemblyEndFaceAnalyzer.cs" />
<Compile Include="src\Utils\Assembly\AssemblyInstallationReferenceBuilder.cs" />
<!-- Core - Configuration Management -->
<Compile Include="src\Core\Config\SystemConfig.cs" />
<Compile Include="src\Core\Config\ConfigManager.cs" />

View File

@ -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<InvalidOperationException>(
() => AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint));
StringAssert.Contains(ex.Message, "距离光轴过小");
}
}
}

View File

@ -133,6 +133,11 @@ namespace NavisworksTransport
/// </summary>
AssemblyGuideLine,
/// <summary>
/// 直线装配安装参考面样式(绿色半透明)
/// </summary>
AssemblyInstallationPlane,
/// <summary>
/// 吊装路径样式(紫色)
/// </summary>
@ -338,6 +343,11 @@ namespace NavisworksTransport
/// </summary>
public List<ObjectSpaceMarker> ObjectSpaceMarkers { get; set; }
/// <summary>
/// 平面标记集合
/// </summary>
public List<PlaneMarker> PlaneMarkers { get; set; }
/// <summary>
/// 是否显示控制点可视化(用户意图)
/// </summary>
@ -358,6 +368,7 @@ namespace NavisworksTransport
PathLineMarkers = new List<LineMarker>();
TangentMarkers = new List<SquareMarker>();
ObjectSpaceMarkers = new List<ObjectSpaceMarker>();
PlaneMarkers = new List<PlaneMarker>();
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);
}
}
/// <summary>
/// 清除网格可视化
/// </summary>
@ -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);
}
/// <summary>
/// 渲染连线标记(支持直线段和圆弧段)
/// </summary>
@ -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;
}
}

View File

@ -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<Point3D> _assemblyEndFaceSeedPoints = new List<Point3D>();
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<Point3D> { 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}), " +

View File

@ -263,6 +263,117 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Border>
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10">
<StackPanel>
<Label Content="终端安装仿真" Style="{StaticResource SectionHeaderStyle}"/>
<Grid Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="安装方式:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<ComboBox Grid.Column="1"
Margin="6,0,8,0"
ItemsSource="{Binding RailMountModeOptions}"
SelectedValue="{Binding AssemblyMountMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
DisplayMemberPath="DisplayName"
ToolTip="选择直线装配路径使用轨上安装还是轨下安装"/>
<Label Grid.Column="2"
Content="垂直偏移:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="3"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="6,0,0,0">
<TextBox Text="{Binding AssemblyAnchorVerticalOffsetInMeters, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="米"
Style="{StaticResource UnitLabelStyle}"/>
</StackPanel>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="球心:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="6,0,8,0">
<TextBox Text="{Binding AssemblySphereCenterX, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="X" Style="{StaticResource UnitLabelStyle}"/>
<TextBox Text="{Binding AssemblySphereCenterY, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="Y" Style="{StaticResource UnitLabelStyle}"/>
<TextBox Text="{Binding AssemblySphereCenterZ, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="Z" Style="{StaticResource UnitLabelStyle}"/>
</StackPanel>
<Label Grid.Column="2"
Content="终点箱体:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="3"
Margin="6,0,0,0"
Text="{Binding AssemblyTerminalObjectName}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="装配起点:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Margin="6,0,0,0"
Text="{Binding AssemblyStartPointText}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
</Grid>
<WrapPanel Margin="0,8,0,0">
<Button Content="捕获箱体"
Command="{Binding CaptureAssemblyTerminalObjectCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
<Button Content="找端面"
Command="{Binding AnalyzeAssemblyTerminalFaceCommand}"
Style="{StaticResource SecondaryButtonStyle}"
IsEnabled="{Binding CanAnalyzeAssemblyTerminalFace}"/>
<Button Content="{Binding AssemblyInstallationPointButtonText}"
Command="{Binding SelectAssemblyInstallationPointCommand}"
Style="{StaticResource SecondaryButtonStyle}"
IsEnabled="{Binding CanSelectAssemblyInstallationPoint}"/>
<Button Content="生成辅助线"
Command="{Binding GenerateAssemblyReferenceRodCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanGenerateAssemblyReferenceRod}"/>
<Button Content="取起点"
Command="{Binding SelectAssemblyStartPointCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanSelectAssemblyStartPoint}"/>
<Button Content="隐藏辅助线"
Command="{Binding ClearAssemblyReferenceRodCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
</WrapPanel>
</StackPanel>
</Border>
@ -300,211 +411,6 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
Foreground="#FF2B579A"
Margin="0,5,0,0"/>
<Border Margin="0,8,0,0"
Padding="8"
BorderBrush="#FFD4E7FF"
BorderThickness="1">
<StackPanel>
<TextBlock Text="终端安装仿真"
FontWeight="SemiBold"
Foreground="#FF2B579A"/>
<TextBlock Text="推荐流程:先选择终点处已安装箱体,再生成一条可点击的辅助线。"
Style="{StaticResource StatusTextStyle}"
Margin="0,4,0,0"/>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="安装方式:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<ComboBox Grid.Column="1"
Margin="6,0,0,0"
ItemsSource="{Binding RailMountModeOptions}"
SelectedValue="{Binding AssemblyMountMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
DisplayMemberPath="DisplayName"
ToolTip="选择直线装配路径使用轨上安装还是轨下安装"/>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="垂直偏移:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Text="{Binding AssemblyAnchorVerticalOffsetInMeters, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Grid.Column="2"
Content="米"
Style="{StaticResource UnitLabelStyle}"/>
<TextBlock Grid.Column="3"
Margin="8,0,0,0"
Text="表示箱体中心到真实安装点沿局部上下方向的距离"
Style="{StaticResource StatusTextStyle}"
VerticalAlignment="Center"
TextWrapping="Wrap"/>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="球心坐标:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Text="{Binding AssemblySphereCenterX, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Grid.Column="2"
Content="X"
Style="{StaticResource UnitLabelStyle}"/>
<TextBox Grid.Column="3"
Text="{Binding AssemblySphereCenterY, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Grid.Column="4"
Content="Y"
Style="{StaticResource UnitLabelStyle}"/>
<TextBox Grid.Column="5"
Text="{Binding AssemblySphereCenterZ, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Grid.Column="6"
Content="Z"
Style="{StaticResource UnitLabelStyle}"/>
<TextBlock Grid.Column="7"
Margin="8,0,0,0"
Text="使用 Navisworks 当前模型坐标,默认 0,0,0"
Style="{StaticResource StatusTextStyle}"
VerticalAlignment="Center"
TextWrapping="Wrap"/>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="终点箱体:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Margin="6,0,0,0"
Text="{Binding AssemblyTerminalObjectName}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="装配起点:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Margin="6,0,0,0"
Text="{Binding AssemblyStartPointText}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="辅助线长度:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Text="{Binding AssemblyReferenceRodLengthInMeters, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Grid.Column="2"
Content="米"
Style="{StaticResource UnitLabelStyle}"/>
<Label Grid.Column="3"
Content="直径:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="4"
Text="{Binding AssemblyReferenceRodDiameterInMeters, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Grid.Column="5"
Content="米"
Style="{StaticResource UnitLabelStyle}"/>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Button Content="捕获终点箱体"
Command="{Binding CaptureAssemblyTerminalObjectCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
<Button Content="生成辅助线"
Command="{Binding GenerateAssemblyReferenceRodCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanGenerateAssemblyReferenceRod}"/>
<Button Content="取起点并生成路径"
Command="{Binding SelectAssemblyStartPointCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanSelectAssemblyStartPoint}"/>
<Button Content="分析端面(3点)"
Command="{Binding AnalyzeAssemblyTerminalFaceCommand}"
Style="{StaticResource SecondaryButtonStyle}"
IsEnabled="{Binding CanAnalyzeAssemblyTerminalFace}"/>
<Button Content="隐藏辅助线"
Command="{Binding ClearAssemblyReferenceRodCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
</StackPanel>
</StackPanel>
</Border>
<Border Margin="0,8,0,0"
Padding="8"
BorderBrush="#FFD4E7FF"
BorderThickness="1"
Visibility="{Binding IsRailRouteSelected, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel>
<TextBlock Text="Rail 路径构型"
FontWeight="SemiBold"
Foreground="#FF2B579A"/>
<TextBlock Text="用于传统空轨路径或已生成的 Rail 直线装配路径。"
Style="{StaticResource StatusTextStyle}"
Margin="0,4,0,0"/>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="安装方式:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<ComboBox Grid.Column="1"
Margin="6,0,0,0"
ItemsSource="{Binding RailMountModeOptions}"
SelectedValue="{Binding SelectedRailMountMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
DisplayMemberPath="DisplayName"
ToolTip="选择安装头位于双轨上方还是下方"/>
</Grid>
</StackPanel>
</Border>
<!-- 路径点列表标题栏和工具按钮 -->
<Grid Margin="0,5,0,0">
<Grid.ColumnDefinitions>

View File

@ -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;
}
}
}

View File

@ -104,5 +104,6 @@ namespace NavisworksTransport.Utils.CoordinateSystem
rotation = convention.CreateQuaternion(forward, up);
return true;
}
}
}