- T07 障碍绕行(AutoPathObstacleDetourAutomationTests):跨模型起终点直线 11.0m→规划 11.57m 绕行,blocked=0 - T08 多策略(同测试类):Shortest/Straightest 参数回传 + 均规划有效路径 - T09 网格诊断(RouteGridDiagnosticsAutomationTests):网格参数/路径点覆盖/段一致性 - T10 持久化(DetectionRecordPersistenceAutomationTests):检测记录重载验证(动画参数 15fps/5s 落库) - 服务端:PathPlanningManager.GetDetectionRecordById + /api/test/detection-record 端点 - 客户端:RunAutoPathAsync 支持 strategy、GetRouteGridDiagnosticsAsync、GetDetectionRecordAsync 验证:集成测试 18/18 通过,完整集 65.8s
183 lines
9.5 KiB
C#
183 lines
9.5 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 自动路径障碍绕行集成测试(覆盖计划 T07)。
|
||
///
|
||
/// 构造起终点直线连线穿过障碍物的场景(跨越大半模型),验证:
|
||
/// - AutoPlanPath 成功规划绕行路径(blockedSampleCount == 0,路径不穿过不可通行网格)
|
||
/// - 规划长度 > 起终点直线距离(确实绕行,而非直线硬穿)
|
||
///
|
||
/// 起终点坐标来自模型内已验证可用的区域(测试基准起点 → 自动测试_Ground 终点),
|
||
/// 直线距离由测试按服务端返回的 metersToModelUnits 换算(不硬编码文档单位)。
|
||
/// </summary>
|
||
[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<bool>("ok"), "导入障碍路径失败: " + (string)importResponse["error"]);
|
||
double metersToModelUnits = importResponse["data"].Value<double>("metersToModelUnits");
|
||
|
||
// 2. 自动路径规划
|
||
JObject response = await client.RunAutoPathAsync(
|
||
"Ground",
|
||
RouteName,
|
||
ObjectLengthInMeters,
|
||
ObjectWidthInMeters,
|
||
SafetyMarginInMeters,
|
||
GridSizeInMeters).ConfigureAwait(false);
|
||
Assert.IsTrue(response.Value<bool>("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<bool>("ok"), "导入障碍路径失败: " + (string)importResponse["error"]);
|
||
|
||
// Shortest 策略
|
||
JObject shortest = await client.RunAutoPathAsync(
|
||
"Ground", RouteName,
|
||
ObjectLengthInMeters, ObjectWidthInMeters, SafetyMarginInMeters, GridSizeInMeters,
|
||
strategy: "Shortest").ConfigureAwait(false);
|
||
Assert.IsTrue(shortest.Value<bool>("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<bool>("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
|
||
{
|
||
}
|
||
}
|
||
}
|
||
}
|