- SectionBoxBatchDetector:路径剖面盒(复用 SectionClipHelper.CalculatePathClipBox 产品语义)→ 导出局部 NWD → 副实例检测 → 报告回传 - 移动物体跨实例解析:导出剖面盒保留节点树结构,去掉根后主/副层级链一致,按 ancestorChain 匹配(用户验证结论) - 副实例临时库:TRANSPORTPLUGIN_TEST_DB_PATH 环境变量显式指定,createIfMissing 时强制创建(解决检测记录无法落库→报告失败) - 副实例进程定位:cmd start detach 后按新 Roamer PID 定位并关闭 - batch-queue-add:真实物体写入 ModelItemReferences(MovingObject),F1 从引用读移动物体名/PathId/层级链 - batch-queue-process:改后台执行立即返回(模态进度条不阻塞 HTTP),调用方轮询数据库状态 - 集成测试标准场景:自动测试_Ground 更新(用户修改)+ Ground 真实物体改 Chair Lounge Couch Double 验证:F1 Ground + Chair Lounge Couch Double,碰撞 28/28,链路完整
142 lines
6.4 KiB
C#
142 lines
6.4 KiB
C#
using System;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||
using Newtonsoft.Json.Linq;
|
||
|
||
namespace NavisworksTransport.UnitTests.Integration
|
||
{
|
||
/// <summary>
|
||
/// 真实物体动画集成测试(覆盖计划 T01-T03,高风险区:真实物体姿态链)。
|
||
///
|
||
/// 验证点:
|
||
/// - 通过 list-model-items 动态解析真实物体(ModelPath(PathId) 跨会话不稳定,禁止硬编码)
|
||
/// - 同名多实例时按“离路径起点最近”选择(如模型中有多个 42" Diameter 管道)
|
||
/// - 真实物体沿三类路径生成动画并完整播放(fragment 代表姿态 / 真实尺寸)
|
||
///
|
||
/// 路径起点坐标来自 resources/auto-test-routes.xml(模型坐标稳定,不随会话变化)。
|
||
/// </summary>
|
||
[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<bool>("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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 枚举同名物体并选择离参考点(路径起点)最近的实例,返回其 PathId。
|
||
/// ModelPath 跨会话不稳定,必须动态解析而非硬编码。
|
||
/// </summary>
|
||
private static async Task<string> 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<bool>("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;
|
||
}
|
||
}
|
||
}
|