测试新增: - T05 Free 自由路径(FreePathAutomationTests:完整动画 + 逐帧位置=路径点) - T06 碰撞报告内容断言(CollisionReportAssertions 接入虚拟/真实物体测试) 服务端增强: - list-model-items 端点(枚举物体:DisplayName/PathId/包围盒中心/祖先链) - run-virtual-collision-test 支持 frameRate/durationSeconds(测试动画加速:15fps/5s) - 测试执行串行锁(SemaphoreSlim,防客户端超时后任务占用 UI 线程并发污染) - 标准路径清单/XML 增加自动测试_Free Bug 修复: - AnimationControlViewModel.InvalidateLastGeneratedReport(跨测试碰撞报告残留) - 真实物体身份校验 ReferenceEquals→InstanceGuid(NW ModelItem 实例不保证同引用) - 真实物体动态解析(ModelPath 跨会话不稳定,禁止硬编码 PathId) - PathImportValidity 测试点 Id 随机化(PathPoints.Id 全局主键,p1/p2 固定会冲突) - start-navisworks.ps1:启动前清理 AutoSave(避免恢复弹窗拦截)、文件关联打开模型、dismiss 兜底、启动 15s→4.4s 验证:集成测试 13/13 通过,完整集 6min→59.6s(动画 15fps/5s)
120 lines
5.3 KiB
C#
120 lines
5.3 KiB
C#
using System;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||
using Newtonsoft.Json.Linq;
|
||
|
||
namespace NavisworksTransport.UnitTests.Integration
|
||
{
|
||
/// <summary>
|
||
/// Free 自由路径集成测试(覆盖计划 T05)。
|
||
///
|
||
/// Free 路径语义:任意多点,每个路径点即物体包围盒中心(轨迹即物体中心,无偏移)。
|
||
/// 验证点:
|
||
/// - 虚拟物体沿 Free 路径完整动画/碰撞流程
|
||
/// - 逐帧位置精确匹配路径点(XYZ 全匹配,区别于 Ground/Hoisting 的中心高度偏移)
|
||
/// </summary>
|
||
[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<bool>("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<bool>("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}");
|
||
}
|
||
}
|
||
}
|