using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; namespace NavisworksTransport.UnitTests.Integration { /// /// Free 自由路径集成测试(覆盖计划 T05)。 /// /// Free 路径语义:任意多点,每个路径点即物体包围盒中心(轨迹即物体中心,无偏移)。 /// 验证点: /// - 虚拟物体沿 Free 路径完整动画/碰撞流程 /// - 逐帧位置精确匹配路径点(XYZ 全匹配,区别于 Ground/Hoisting 的中心高度偏移) /// [TestClass] [TestCategory("NavisworksIntegration")] public class FreePathAutomationTests { private const double Tolerance = 1e-2; private const int TestFrameRate = 15; private const double TestDurationSeconds = 5.0; [TestMethod] [Timeout(360000)] public async Task FreeVirtualObject_Animation_Completes() { using (var client = new NavisworksTestAutomationClient()) { await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false); JObject response = await client.RunCollisionTestAsync( "Free", 240, frameRate: TestFrameRate, durationSeconds: TestDurationSeconds).ConfigureAwait(false); Assert.IsTrue(response.Value("ok"), "Free 路径碰撞测试失败: " + (string)response["error"]); JObject data = (JObject)response["data"]; JObject route = (JObject)data["route"]; Assert.IsNotNull(route, "缺少 route"); Assert.AreEqual("Free", (string)route["pathType"], "返回的路径类型不匹配"); Assert.AreEqual("自动测试_Free", (string)route["name"], "应使用标准 Free 测试路径"); JObject animation = (JObject)data["animation"]; Assert.AreEqual("Finished", (string)animation["currentState"], "动画未完成"); Assert.IsTrue((int)animation["totalFrames"] > 0, "动画总帧数应大于 0"); CollisionReportAssertions.AssertReportStructure(data, route); } } [TestMethod] [Timeout(240000)] public async Task FreeVirtualObject_FramesMatchPathPoints() { using (var client = new NavisworksTestAutomationClient()) { await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false); JObject response = await client.ProbeAnimationFramesAsync( "Free", 120, "0,0.5,1.0", TestFrameRate, TestDurationSeconds).ConfigureAwait(false); Assert.IsTrue(response.Value("ok"), "帧探针失败: " + (string)response["error"]); JObject data = (JObject)response["data"]; Assert.AreEqual("自动测试_Free", (string)data["route"]["name"], "探针路径不匹配"); JObject pathStart = (JObject)data["pathStart"]; JObject pathEnd = (JObject)data["pathEnd"]; JArray probes = (JArray)data["probes"]; Assert.IsNotNull(probes, "缺少 probes"); Assert.AreEqual(3, probes.Count, "探针点数应为 3(0/0.5/1.0)"); var p0 = (JObject)probes[0]["position"]; var pm = (JObject)probes[1]["position"]; var p1 = (JObject)probes[2]["position"]; // Free 路径:物体中心 = 路径点(XYZ 全匹配) AssertNear((double)pathStart["x"], (double)p0["x"], "起点 X"); AssertNear((double)pathStart["y"], (double)p0["y"], "起点 Y"); AssertNear((double)pathStart["z"], (double)p0["z"], "起点 Z"); AssertNear((double)pathEnd["x"], (double)p1["x"], "终点 X"); AssertNear((double)pathEnd["y"], (double)p1["y"], "终点 Y"); AssertNear((double)pathEnd["z"], (double)p1["z"], "终点 Z"); // 中间帧在起终点之间(3D 距离) double startToMid = Distance3D(p0, pm); double startToEnd = Distance3D(p0, p1); Assert.IsTrue(startToMid > Tolerance, "中间帧不应等于起点"); Assert.IsTrue(startToMid < startToEnd - Tolerance, "中间帧应在起终点之间"); // yaw 应为有限值(Free 路径保持物体姿态) foreach (var probe in probes) { Assert.IsFalse(double.IsNaN((double)probe["yawDegrees"]), "yaw 不应为 NaN"); } } } private static double Distance3D(JObject a, JObject b) { double dx = (double)a["x"] - (double)b["x"]; double dy = (double)a["y"] - (double)b["y"]; double dz = (double)a["z"] - (double)b["z"]; return Math.Sqrt(dx * dx + dy * dy + dz * dz); } private static void AssertNear(double expected, double actual, string message) { Assert.IsTrue( Math.Abs(expected - actual) <= Tolerance, $"{message}:期望 {expected:F3},实际 {actual:F3},容差 {Tolerance}"); } } }