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
{
}
}
}
}