NavisworksTransport/UnitTests/Integration/AnimationFrameProbeAutomationTests.cs
tian 8f477c0e1c feat: 集成测试新增动画逐帧状态验证(P0 T04)
- 服务端新增 probe-animation-frames 端点:生成动画后 SeekToProgress 到多个进度点,返回物体跟踪位置/yaw
- 新增 AnimationFrameProbeAutomationTests:
  - Ground:起点/终点 XZ 匹配路径点、高度恒定(平面链)、中间帧在起终点之间
  - Hoisting:吊装剖面(起吊地面→悬挂升高→下降落地)+ 水平平移
- 集成测试 11/11 通过(P0 高风险区全部完成:真实物体 + 逐帧状态)
2026-08-04 17:11:57 +08:00

140 lines
7.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
namespace NavisworksTransport.UnitTests.Integration
{
/// <summary>
/// 动画逐帧状态验证集成测试(覆盖计划 T04
///
/// 通过 probe-animation-frames 端点生成动画后 seek 到多个进度点,
/// 断言物体跟踪位置(业务跟踪点 = 原始包围盒中心)沿路径移动:
/// - Ground起点/终点 XZ 匹配路径点,高度恒定(平面链不改变高度),中间帧在起终点之间
/// - Hoisting高度剖面起吊地面 → 悬挂升高 → 下降地面)+ 水平平移
///
/// 注比较使用模型单位Floor2 为英尺);物体跟踪点 XZ 与路径点一致Y 为中心高度(非路径点高度)。
/// </summary>
[TestClass]
[TestCategory("NavisworksIntegration")]
public class AnimationFrameProbeAutomationTests
{
private const double PositionTolerance = 1e-2;
[TestMethod]
[Timeout(240000)]
public async Task GroundVirtualObject_FramesFollowPathStartToEnd()
{
using (var client = new NavisworksTestAutomationClient())
{
await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);
JObject response = await client.ProbeAnimationFramesAsync("Ground", 120).ConfigureAwait(false);
Assert.IsTrue(response.Value<bool>("ok"), "帧探针失败: " + (string)response["error"]);
JObject data = (JObject)response["data"];
Assert.AreEqual("自动测试_Ground", (string)data["route"]["name"], "探针路径不匹配");
Assert.IsTrue((int)data["totalFrames"] > 0, "总帧数应大于 0");
JObject pathStart = (JObject)data["pathStart"];
JObject pathEnd = (JObject)data["pathEnd"];
JArray probes = (JArray)data["probes"];
AssertProbeCount(probes, 5);
// probe 0物体起点 = 路径起点XZ 匹配Y 为中心高度)
var p0 = (JObject)probes[0]["position"];
AssertNear((double)pathStart["x"], (double)p0["x"], PositionTolerance, "起点 X 不匹配路径起点");
AssertNear((double)pathStart["z"], (double)p0["z"], PositionTolerance, "起点 Z 不匹配路径起点");
// probe 1.0:物体终点 = 路径终点
var p1 = (JObject)probes[probes.Count - 1]["position"];
AssertNear((double)pathEnd["x"], (double)p1["x"], PositionTolerance, "终点 X 不匹配路径终点");
AssertNear((double)pathEnd["z"], (double)p1["z"], PositionTolerance, "终点 Z 不匹配路径终点");
// Ground 平面链:高度恒定(物体中心高度不变)
AssertNear((double)p0["y"], (double)p1["y"], PositionTolerance, "Ground 路径高度应恒定");
// 中间帧在起终点之间(水平距离大于 0 且小于全程)
var pm = (JObject)probes[probes.Count / 2]["position"];
double startToMid = HorizontalDistance(p0, pm);
double startToEnd = HorizontalDistance(p0, p1);
Assert.IsTrue(startToMid > PositionTolerance, "中间帧不应等于起点");
Assert.IsTrue(startToMid < startToEnd - PositionTolerance, "中间帧应在起终点之间");
// yaw 应为有限值
foreach (var probe in probes)
{
Assert.IsFalse(double.IsNaN((double)probe["yawDegrees"]), "yaw 不应为 NaN");
}
}
}
[TestMethod]
[Timeout(240000)]
public async Task HoistingVirtualObject_FramesFollowLiftProfile()
{
using (var client = new NavisworksTestAutomationClient())
{
await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);
JObject response = await client.ProbeAnimationFramesAsync("Hoisting", 120).ConfigureAwait(false);
Assert.IsTrue(response.Value<bool>("ok"), "帧探针失败: " + (string)response["error"]);
JObject data = (JObject)response["data"];
Assert.AreEqual("自动测试_Hoisting", (string)data["route"]["name"], "探针路径不匹配");
Assert.IsTrue((int)data["totalFrames"] > 0, "总帧数应大于 0");
JObject pathStart = (JObject)data["pathStart"];
JObject pathEnd = (JObject)data["pathEnd"];
JArray probes = (JArray)data["probes"];
AssertProbeCount(probes, 5);
var p0 = (JObject)probes[0]["position"];
var pm = (JObject)probes[probes.Count / 2]["position"];
var p1 = (JObject)probes[probes.Count - 1]["position"];
// 起吊点/落地点XZ 匹配路径起点/终点(地面)
AssertNear((double)pathStart["x"], (double)p0["x"], PositionTolerance, "起吊点 X 不匹配");
AssertNear((double)pathStart["z"], (double)p0["z"], PositionTolerance, "起吊点 Z 不匹配");
AssertNear((double)pathEnd["x"], (double)p1["x"], PositionTolerance, "落地点 X 不匹配");
AssertNear((double)pathEnd["z"], (double)p1["z"], PositionTolerance, "落地点 Z 不匹配");
// 吊装剖面:中间帧高度高于起吊/落地(悬挂段),结束后回落
Assert.IsTrue((double)pm["y"] > (double)p0["y"] + PositionTolerance, "中间帧应高于起吊点(悬挂升高)");
AssertNear((double)p0["y"], (double)p1["y"], PositionTolerance, "落地后高度应回落到起吊高度");
// 水平平移:中间帧 X 在起点与终点之间
double startX = (double)p0["x"];
double endX = (double)p1["x"];
double midX = (double)pm["x"];
Assert.IsTrue(
midX > Math.Min(startX, endX) - PositionTolerance &&
midX < Math.Max(startX, endX) + PositionTolerance,
"中间帧 X 应在起终点之间(水平平移)");
}
}
private static void AssertProbeCount(JArray probes, int expected)
{
Assert.IsNotNull(probes, "缺少 probes");
Assert.AreEqual(expected, probes.Count, $"探针点数应为 {expected}0/0.25/0.5/0.75/1.0");
}
private static double HorizontalDistance(JObject a, JObject b)
{
double dx = (double)a["x"] - (double)b["x"];
double dz = (double)a["z"] - (double)b["z"];
return Math.Sqrt(dx * dx + dz * dz);
}
private static void AssertNear(double expected, double actual, double tolerance, string message)
{
Assert.IsTrue(
Math.Abs(expected - actual) <= tolerance,
$"{message}:期望 {expected:F3},实际 {actual:F3},容差 {tolerance}");
}
}
}