From 4f9206da2a6358a71668dd4a83ed0ba0af87c242 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Wed, 8 Jul 2026 12:55:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=87=E5=BC=AF=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=EF=BC=88=E7=BA=AF=E8=A7=A3=E6=9E=90=E5=87=A0?= =?UTF-8?q?=E4=BD=95=E5=85=AC=E5=BC=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NavisworksTransport.UnitTests.csproj | 1 + TransportPlugin.csproj | 8 ++ UnitTests/Core/CornerTurnCheckHelperTests.cs | 99 ++++++++++++++ src/UI/WPF/Resources/NavisworksStyles.xaml | 3 + src/UI/WPF/Views/CornerTurnCheckDialog.xaml | 123 ++++++++++++++++++ .../WPF/Views/CornerTurnCheckDialog.xaml.cs | 80 ++++++++++++ src/UI/WPF/Views/PathEditingView.xaml | 14 +- src/UI/WPF/Views/PathEditingView.xaml.cs | 13 ++ src/Utils/CornerTurnCheckHelper.cs | 98 ++++++++++++++ 9 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 UnitTests/Core/CornerTurnCheckHelperTests.cs create mode 100644 src/UI/WPF/Views/CornerTurnCheckDialog.xaml create mode 100644 src/UI/WPF/Views/CornerTurnCheckDialog.xaml.cs create mode 100644 src/Utils/CornerTurnCheckHelper.cs diff --git a/NavisworksTransport.UnitTests.csproj b/NavisworksTransport.UnitTests.csproj index 9408696..9f5f4f1 100644 --- a/NavisworksTransport.UnitTests.csproj +++ b/NavisworksTransport.UnitTests.csproj @@ -62,6 +62,7 @@ + diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj index 95c37bc..3564ada 100644 --- a/TransportPlugin.csproj +++ b/TransportPlugin.csproj @@ -278,6 +278,9 @@ EditCoordinatesWindow.xaml + + CornerTurnCheckDialog.xaml + ModelItemBoundsWindow.xaml @@ -342,6 +345,7 @@ + @@ -407,6 +411,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/UnitTests/Core/CornerTurnCheckHelperTests.cs b/UnitTests/Core/CornerTurnCheckHelperTests.cs new file mode 100644 index 0000000..e95ca69 --- /dev/null +++ b/UnitTests/Core/CornerTurnCheckHelperTests.cs @@ -0,0 +1,99 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NavisworksTransport.Utils; + +namespace NavisworksTransport.UnitTests.Core +{ + [TestClass] + public class CornerTurnCheckHelperTests + { + [TestMethod] + public void EqualWidth_MaxLength_ShouldPass() + { + var L = 3.0 * Math.Sqrt(2) - 1.5 - 0.01; + Assert.IsTrue(CornerTurnCheckHelper.CanPass(L, 1.5, 3.0, 3.0, out _, out _)); + } + + [TestMethod] + public void EqualWidth_TooLong_ShouldNotPass() + { + Assert.IsFalse(CornerTurnCheckHelper.CanPass(2.5, 1.0, 2.0, 2.0, out _, out _)); + } + + [TestMethod] + public void EqualWidth_Short_ShouldPass() + { + Assert.IsTrue(CornerTurnCheckHelper.CanPass(1.0, 0.5, 2.0, 2.0, out _, out _)); + } + + [TestMethod] + public void StraightPass_ShouldPass() + { + Assert.IsTrue(CornerTurnCheckHelper.CanPass(2.5, 0.3, 2.5, 3.0, out _, out _)); + } + + [TestMethod] + public void WidthExceedsBoth_ShouldNotPass() + { + Assert.IsFalse(CornerTurnCheckHelper.CanPass(3.0, 4.0, 2.0, 2.5, out _, out _)); + } + + [TestMethod] + public void ZeroOrNegativeInput_ShouldReturnFalse() + { + Assert.IsFalse(CornerTurnCheckHelper.CanPass(0, 1, 2, 2, out _, out _)); + Assert.IsFalse(CornerTurnCheckHelper.CanPass(2, 0, 2, 2, out _, out _)); + Assert.IsFalse(CornerTurnCheckHelper.CanPass(2, 1, 0, 2, out _, out _)); + Assert.IsFalse(CornerTurnCheckHelper.CanPass(2, 1, 2, 0, out _, out _)); + Assert.IsFalse(CornerTurnCheckHelper.CanPass(-1, 1, 2, 2, out _, out _)); + } + + [TestMethod] + public void UnequalWidth_Fits_ShouldPass() + { + Assert.IsTrue(CornerTurnCheckHelper.CanPass(0.8, 0.5, 3.0, 1.5, out _, out _)); + } + + [TestMethod] + public void VeryDifferentWidths_ShouldUseNarrower() + { + Assert.IsTrue(CornerTurnCheckHelper.CanPass(0.1, 0.55, 10.0, 0.6, out _, out _)); + } + + [TestMethod] + public void HugeModule_ShouldNotPass() + { + Assert.IsFalse(CornerTurnCheckHelper.CanPass(20.0, 5.0, 2.0, 2.0, out _, out _)); + } + + [TestMethod] + public void UserCase_LongerThanEntry_ShouldNotPass() + { + Assert.IsFalse(CornerTurnCheckHelper.CanPass(4.9, 2.0, 4.8, 2.75, out _, out _)); + } + + [TestMethod] + public void UserCase_WiderThanExit_ShouldNotPass() + { + Assert.IsFalse(CornerTurnCheckHelper.CanPass(4.8, 2.8, 3.0, 2.75, out _, out _)); + } + + [TestMethod] + public void EqualWidth_Square_ShouldPass() + { + Assert.IsTrue(CornerTurnCheckHelper.CanPass(1.0, 1.0, 2.0, 2.0, out _, out _)); + } + + [TestMethod] + public void EntryExact_ShouldPass() + { + Assert.IsTrue(CornerTurnCheckHelper.CanPass(2.75, 2.0, 5.0, 2.75, out _, out _)); + } + + [TestMethod] + public void Square_WidthEqualsCorridor_ShouldPass() + { + Assert.IsTrue(CornerTurnCheckHelper.CanPass(2.0, 2.0, 3.0, 2.75, out _, out _)); + } + } +} diff --git a/src/UI/WPF/Resources/NavisworksStyles.xaml b/src/UI/WPF/Resources/NavisworksStyles.xaml index 39f58ae..8098444 100644 --- a/src/UI/WPF/Resources/NavisworksStyles.xaml +++ b/src/UI/WPF/Resources/NavisworksStyles.xaml @@ -209,6 +209,9 @@ NavisworksTransport 共享样式资源字典 - Navisworks 2026风格 M17.65,6.35C16.2,4.9 14.21,4 12,4C7.58,4 4.01,7.58 4.01,12C4.01,16.42 7.58,20 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18C8.69,18 6,15.31 6,12C6,8.69 8.69,6 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z + + M3,17 L17,17 L17,3 + M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2ZM12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20ZM12,6C8.69,6 6,8.69 6,12C6,15.31 8.69,18 12,18C15.31,18 18,15.31 18,12C18,8.69 15.31,6 12,6ZM12,16C9.79,16 8,14.21 8,12C8,9.79 9.79,8 12,8C14.21,8 16,9.79 16,12C16,14.21 14.21,16 12,16Z diff --git a/src/UI/WPF/Views/CornerTurnCheckDialog.xaml b/src/UI/WPF/Views/CornerTurnCheckDialog.xaml new file mode 100644 index 0000000..cd5fb45 --- /dev/null +++ b/src/UI/WPF/Views/CornerTurnCheckDialog.xaml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/UI/WPF/Views/PathEditingView.xaml.cs b/src/UI/WPF/Views/PathEditingView.xaml.cs index 717545d..aab45e7 100644 --- a/src/UI/WPF/Views/PathEditingView.xaml.cs +++ b/src/UI/WPF/Views/PathEditingView.xaml.cs @@ -286,6 +286,19 @@ namespace NavisworksTransport.UI.WPF.Views } } + private void OnCornerTurnCheckClick(object sender, RoutedEventArgs e) + { + try + { + var dialog = new CornerTurnCheckDialog(); + dialog.ShowDialog(); + } + catch (Exception ex) + { + LogManager.Error($"打开过弯检测对话框失败: {ex.Message}"); + } + } + /// /// 包围盒信息窗口实例(保持引用以防止被GC回收) /// diff --git a/src/Utils/CornerTurnCheckHelper.cs b/src/Utils/CornerTurnCheckHelper.cs new file mode 100644 index 0000000..a5bfccd --- /dev/null +++ b/src/Utils/CornerTurnCheckHelper.cs @@ -0,0 +1,98 @@ +using System; + +namespace NavisworksTransport.Utils +{ + public static class CornerTurnCheckHelper + { + public static bool CanPass(double length, double width, double entryWidth, double exitWidth, + out double margin, out double maxLength) + { + margin = 0; + maxLength = 0; + + if (length <= 0 || width <= 0 || entryWidth <= 0 || exitWidth <= 0) + return false; + + // 模块最短边必须能同时通过两条通道 + var minDim = Math.Min(length, width); + if (minDim > entryWidth || minDim > exitWidth) + return false; + + // 转角通过性解析判断 + var r = Math.Sqrt(length * length + width * width); + var phi = Math.Atan2(width, length); + + var sinEntryRatio = entryWidth / r; + var cosExitRatio = exitWidth / r; + + // 入口侧:最大 θ 满足 Y_span ≤ C₁ + var thetaYmax = double.NaN; + if (sinEntryRatio >= 1) + thetaYmax = Math.PI / 2; // 从不超出 + else + { + thetaYmax = Math.Asin(sinEntryRatio) - phi; + if (thetaYmax < 0) thetaYmax = 0; + if (thetaYmax > Math.PI / 2) thetaYmax = Math.PI / 2; + } + + // 出口侧:最小 θ 满足 X_span ≤ C₂ + var thetaXmin = double.NaN; + if (cosExitRatio >= 1) + thetaXmin = 0; // 从不超出 + else + { + thetaXmin = Math.Acos(cosExitRatio) + phi; + if (thetaXmin < 0) thetaXmin = 0; + if (thetaXmin > Math.PI / 2) thetaXmin = Math.PI / 2; + } + + // 需要同时满足两条约束的区间有交集 + var canPass = thetaYmax >= thetaXmin; + + if (canPass) + { + // 余量 = 在交集中心处两个约束的最小剩余量 + var thetaMid = (thetaYmax + thetaXmin) / 2; + var cos = Math.Cos(thetaMid); + var sin = Math.Sin(thetaMid); + var xSpan = length * cos + width * sin; + var ySpan = length * sin + width * cos; + margin = Math.Min(exitWidth - xSpan, entryWidth - ySpan); + + maxLength = FindMaxLength(width, entryWidth, exitWidth); + } + + return canPass; + } + + /// 二分法求最大可通过长度,使用相同的解析条件 + private static double FindMaxLength(double width, double c1, double c2) + { + double lo = 0, hi = Math.Max(c1, c2) * 4; + for (int i = 0; i < 50; i++) + { + var mid = (lo + hi) / 2; + if (CheckLengthFeasible(mid, width, c1, c2)) + lo = mid; + else + hi = mid; + } + return (lo + hi) / 2; + } + + private static bool CheckLengthFeasible(double length, double width, double c1, double c2) + { + var r = Math.Sqrt(length * length + width * width); + var phi = Math.Atan2(width, length); + + var thetaYmax = c1 >= r ? Math.PI / 2 + : Math.Min(Math.Max(Math.Asin(c1 / r) - phi, 0), Math.PI / 2); + + var thetaXmin = c2 >= r ? 0 + : Math.Min(Math.Max(Math.Acos(c2 / r) + phi, 0), Math.PI / 2); + + return thetaYmax >= thetaXmin; + } + } +}