using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; namespace NavisworksTransport.UnitTests.Integration { /// /// 真实物体动画集成测试(覆盖计划 T01-T03,高风险区:真实物体姿态链)。 /// /// 验证点: /// - 通过 list-model-items 动态解析真实物体(ModelPath(PathId) 跨会话不稳定,禁止硬编码) /// - 同名多实例时按“离路径起点最近”选择(如模型中有多个 42" Diameter 管道) /// - 真实物体沿三类路径生成动画并完整播放(fragment 代表姿态 / 真实尺寸) /// /// 路径起点坐标来自 resources/auto-test-routes.xml(模型坐标稳定,不随会话变化)。 /// [TestClass] [TestCategory("NavisworksIntegration")] public class RealObjectAnimationAutomationTests { private const int TestFrameRate = 15; private const double TestDurationSeconds = 5.0; // 路径起点(模型单位,来自 auto-test-routes.xml) private static readonly (double X, double Y, double Z) GroundStart = (-216.959, 14.833, 7.698); private static readonly (double X, double Y, double Z) HoistingStart = (-221.750, 10.833, -14.097); private static readonly (double X, double Y, double Z) RailStart = (-207.591, 29.482, 3.504); [TestMethod] [Timeout(360000)] public async Task GroundRealObject_Animation_Completes() { await AssertRealObjectFlowAsync("Ground", "Chair Lounge Couch Double", GroundStart).ConfigureAwait(false); } [TestMethod] [Timeout(360000)] public async Task HoistingRealObject_Animation_Completes() { await AssertRealObjectFlowAsync("Hoisting", "25\" x 25\"", HoistingStart).ConfigureAwait(false); } [TestMethod] [Timeout(360000)] public async Task RailRealObject_Animation_Completes() { await AssertRealObjectFlowAsync("Rail", "42\" Diameter", RailStart).ConfigureAwait(false); } private static async Task AssertRealObjectFlowAsync( string pathType, string objectDisplayName, (double X, double Y, double Z) routeStart) { using (var client = new NavisworksTestAutomationClient()) { await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false); // 动态解析真实物体 PathId(离路径起点最近的同名实例) string animatedObjectPath = await ResolveRealObjectPathIdAsync( client, objectDisplayName, routeStart).ConfigureAwait(false); JObject response = await client.RunCollisionTestAsync( pathType, 240, useVirtualObject: false, animatedObjectPath: animatedObjectPath, frameRate: TestFrameRate, durationSeconds: TestDurationSeconds).ConfigureAwait(false); Assert.IsTrue(response.Value("ok"), "真实物体碰撞测试 HTTP 接口返回失败: " + (string)response["error"]); JObject data = (JObject)response["data"]; Assert.IsNotNull(data, "缺少测试结果 data"); JObject animatedObject = (JObject)data["animatedObject"]; Assert.IsNotNull(animatedObject, "缺少 animatedObject"); Assert.AreEqual("RealObject", (string)animatedObject["mode"], "当前测试应使用真实物体模式"); Assert.IsFalse(string.IsNullOrWhiteSpace((string)animatedObject["displayName"]), "真实物体显示名为空"); JObject route = (JObject)data["route"]; Assert.IsNotNull(route, "缺少 route"); Assert.AreEqual(pathType, (string)route["pathType"], "返回的路径类型不匹配"); JObject animation = (JObject)data["animation"]; Assert.IsNotNull(animation, "缺少 animation"); Assert.AreEqual("Finished", (string)animation["currentState"], "动画未完成"); Assert.IsTrue((int)animation["totalFrames"] > 0, "动画总帧数应大于 0"); Assert.IsTrue(animation["detectionRecordId"] != null && (int)animation["detectionRecordId"] > 0, "检测记录 ID 无效"); CollisionReportAssertions.AssertReportStructure(data, route); } } /// /// 枚举同名物体并选择离参考点(路径起点)最近的实例,返回其 PathId。 /// ModelPath 跨会话不稳定,必须动态解析而非硬编码。 /// private static async Task ResolveRealObjectPathIdAsync( NavisworksTestAutomationClient client, string displayName, (double X, double Y, double Z) referencePoint) { JObject response = await client.ListModelItemsAsync(displayName, 100).ConfigureAwait(false); Assert.IsTrue(response.Value("ok"), "枚举模型物体失败: " + (string)response["error"]); JObject data = (JObject)response["data"]; JArray items = data["items"] as JArray; Assert.IsNotNull(items, "缺少 items"); Assert.IsTrue(items.Count > 0, $"未找到物体: {displayName}"); string bestPathId = null; double bestDistanceSq = double.MaxValue; foreach (JToken token in items) { JObject item = (JObject)token; JObject center = (JObject)item["boundingBoxCenter"]; string pathId = (string)item["pathId"]; if (center == null || string.IsNullOrWhiteSpace(pathId)) { continue; } double dx = (double)center["x"] - referencePoint.X; double dy = (double)center["y"] - referencePoint.Y; double dz = (double)center["z"] - referencePoint.Z; double distanceSq = dx * dx + dy * dy + dz * dz; if (distanceSq < bestDistanceSq) { bestDistanceSq = distanceSq; bestPathId = pathId; } } Assert.IsNotNull(bestPathId, $"无法定位物体(无包围盒中心): {displayName}"); return bestPathId; } } }