using System; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; namespace NavisworksTransport.UnitTests.Integration { /// /// 自动路径障碍绕行集成测试(覆盖计划 T07)。 /// /// 构造起终点直线连线穿过障碍物的场景(跨越大半模型),验证: /// - AutoPlanPath 成功规划绕行路径(blockedSampleCount == 0,路径不穿过不可通行网格) /// - 规划长度 > 起终点直线距离(确实绕行,而非直线硬穿) /// /// 起终点坐标来自模型内已验证可用的区域(测试基准起点 → 自动测试_Ground 终点), /// 直线距离由测试按服务端返回的 metersToModelUnits 换算(不硬编码文档单位)。 /// [TestClass] [TestCategory("NavisworksIntegration")] public class AutoPathObstacleDetourAutomationTests { private const string RouteName = "障碍绕行_测试"; // 模型单位坐标:跨越大半模型的起终点(直线连线穿过障碍区域) private const double StartX = -204.608; private const double StartY = 14.833; private const double StartZ = 19.250; private const double EndX = -177.130; private const double EndY = 14.833; private const double EndZ = -4.383; private const double ObjectLengthInMeters = 0.4; private const double ObjectWidthInMeters = 0.4; private const double SafetyMarginInMeters = 0.05; private const double GridSizeInMeters = 0.3; [TestMethod] [Timeout(240000)] public async Task GroundAutoPath_ObstacleRoute_DetoursAroundObstacles() { using (var client = new NavisworksTestAutomationClient()) { await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false); string tempDir = Path.Combine(Path.GetTempPath(), "NavisworksTransportIntegrationTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(tempDir); try { // 1. 导入障碍起终点路径(overwrite 保证可重复执行) string filePath = Path.Combine(tempDir, "obstacle-route.json"); File.WriteAllText(filePath, BuildObstacleRouteJson(), new UTF8Encoding(false)); JObject importResponse = await client.ImportRouteFileAsync(filePath, overwrite: true).ConfigureAwait(false); Assert.IsTrue(importResponse.Value("ok"), "导入障碍路径失败: " + (string)importResponse["error"]); double metersToModelUnits = importResponse["data"].Value("metersToModelUnits"); // 2. 自动路径规划 JObject response = await client.RunAutoPathAsync( "Ground", RouteName, ObjectLengthInMeters, ObjectWidthInMeters, SafetyMarginInMeters, GridSizeInMeters).ConfigureAwait(false); Assert.IsTrue(response.Value("ok"), "自动路径规划失败: " + (string)response["error"]); JObject data = (JObject)response["data"]; // 3. 路径成功绕行:不穿过不可通行网格 JObject segmentValidation = (JObject)data["segmentValidation"]; Assert.IsNotNull(segmentValidation, "缺少 segmentValidation"); Assert.AreEqual(0, (int)segmentValidation["blockedSampleCount"], "绕行路径仍穿过不可通行网格"); Assert.AreEqual(0, (int)segmentValidation["invalidSampleCount"], "绕行路径存在落在网格外的采样点"); // 4. 确实绕行:规划长度 > 起终点直线距离(换算为米) double straightLineModelUnits = Math.Sqrt( (EndX - StartX) * (EndX - StartX) + (EndY - StartY) * (EndY - StartY) + (EndZ - StartZ) * (EndZ - StartZ)); double straightLineMeters = straightLineModelUnits / metersToModelUnits; double plannedLength = (double)data["generatedRoute"]["length"]; Assert.IsTrue( plannedLength > straightLineMeters * 1.02, $"规划路径长度 {plannedLength:F3}m 应明显大于直线距离 {straightLineMeters:F3}m(未绕行?)"); } finally { SafeDelete(tempDir); } } } [TestMethod] [Timeout(240000)] public async Task GroundAutoPath_Strategies_ShortestAndStraightestBothSucceed() { using (var client = new NavisworksTestAutomationClient()) { await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false); string tempDir = Path.Combine(Path.GetTempPath(), "NavisworksTransportIntegrationTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(tempDir); try { string filePath = Path.Combine(tempDir, "obstacle-route.json"); File.WriteAllText(filePath, BuildObstacleRouteJson(), new UTF8Encoding(false)); JObject importResponse = await client.ImportRouteFileAsync(filePath, overwrite: true).ConfigureAwait(false); Assert.IsTrue(importResponse.Value("ok"), "导入障碍路径失败: " + (string)importResponse["error"]); // Shortest 策略 JObject shortest = await client.RunAutoPathAsync( "Ground", RouteName, ObjectLengthInMeters, ObjectWidthInMeters, SafetyMarginInMeters, GridSizeInMeters, strategy: "Shortest").ConfigureAwait(false); Assert.IsTrue(shortest.Value("ok"), "Shortest 策略规划失败: " + (string)shortest["error"]); // Straightest 策略 JObject straightest = await client.RunAutoPathAsync( "Ground", RouteName, ObjectLengthInMeters, ObjectWidthInMeters, SafetyMarginInMeters, GridSizeInMeters, strategy: "Straightest").ConfigureAwait(false); Assert.IsTrue(straightest.Value("ok"), "Straightest 策略规划失败: " + (string)straightest["error"]); // 策略参数回传正确 Assert.AreEqual("Shortest", (string)shortest["data"]["planningParameters"]["strategy"], "Shortest 策略参数未回传"); Assert.AreEqual("Straightest", (string)straightest["data"]["planningParameters"]["strategy"], "Straightest 策略参数未回传"); // 两种策略均规划出有效路径(不穿过障碍) AssertValidPlannedPath(shortest, "Shortest"); AssertValidPlannedPath(straightest, "Straightest"); } finally { SafeDelete(tempDir); } } } private static void AssertValidPlannedPath(JObject response, string strategyName) { JObject data = (JObject)response["data"]; JObject generatedRoute = (JObject)data["generatedRoute"]; Assert.IsTrue((int)generatedRoute["pointCount"] >= 2, $"{strategyName} 路径至少应包含起点和终点"); Assert.IsTrue((double)generatedRoute["length"] > 0, $"{strategyName} 路径长度必须大于 0"); JObject segmentValidation = (JObject)data["segmentValidation"]; Assert.AreEqual(0, (int)segmentValidation["blockedSampleCount"], $"{strategyName} 路径穿过不可通行网格"); Assert.AreEqual(0, (int)segmentValidation["invalidSampleCount"], $"{strategyName} 路径存在网格外采样点"); } private static string BuildObstacleRouteJson() { return string.Format( System.Globalization.CultureInfo.InvariantCulture, @"{{""PathPlanningData"":{{""version"":""1.0"",""generator"":""integration-test"",""timestamp"":""2026-08-04T21:30:00"",""ProjectInfo"":{{""name"":""test"",""description"":""test"",""units"":""meters"",""coordinateSystem"":""Global""}},""Routes"":[{{""id"":""obstacle-route-{0}"",""name"":""{1}"",""description"":"""",""pathType"":""Ground"",""totalLength"":40.0,""objectLimits"":{{""maxLength"":0,""maxWidth"":0,""maxHeight"":0,""safetyMargin"":0}},""gridSize"":1.0,""liftHeight"":0.0,""created"":""2026-08-04T21:30:00"",""points"":[{{""id"":""ob-p1"",""name"":""start"",""type"":""StartPoint"",""index"":0,""x"":{2},""y"":{3},""z"":{4},""created"":""2026-08-04T21:30:00""}},{{""id"":""ob-p2"",""name"":""end"",""type"":""EndPoint"",""index"":1,""x"":{5},""y"":{6},""z"":{7},""created"":""2026-08-04T21:30:00""}}]}}]}}}}", Guid.NewGuid().ToString("N"), RouteName, StartX, StartY, StartZ, EndX, EndY, EndZ); } private static void SafeDelete(string directory) { if (!Directory.Exists(directory)) { return; } try { Directory.Delete(directory, true); } catch { } } } }