NavisworksTransport/UnitTests/Integration/FreePathAutomationTests.cs
tian 44214baa00 feat: 集成测试补 T05/T06 + 动画加速与多项基础设施修复
测试新增:
- 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)
2026-08-04 18:13:26 +08:00

120 lines
5.3 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>
/// 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, "探针点数应为 30/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}");
}
}
}