- 服务端新增 probe-animation-frames 端点:生成动画后 SeekToProgress 到多个进度点,返回物体跟踪位置/yaw - 新增 AnimationFrameProbeAutomationTests: - Ground:起点/终点 XZ 匹配路径点、高度恒定(平面链)、中间帧在起终点之间 - Hoisting:吊装剖面(起吊地面→悬挂升高→下降落地)+ 水平平移 - 集成测试 11/11 通过(P0 高风险区全部完成:真实物体 + 逐帧状态)
198 lines
7.5 KiB
C#
198 lines
7.5 KiB
C#
using System;
|
||
using System.Globalization;
|
||
using System.Net.Http;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||
using Newtonsoft.Json.Linq;
|
||
|
||
namespace NavisworksTransport.UnitTests.Integration
|
||
{
|
||
internal sealed class NavisworksTestAutomationClient : IDisposable
|
||
{
|
||
private readonly HttpClient _httpClient;
|
||
|
||
public NavisworksTestAutomationClient()
|
||
{
|
||
_httpClient = new HttpClient
|
||
{
|
||
BaseAddress = new Uri("http://127.0.0.1:18777"),
|
||
Timeout = TimeSpan.FromSeconds(20)
|
||
};
|
||
}
|
||
|
||
public async Task EnsureServiceReadyAsync(TimeSpan timeout)
|
||
{
|
||
DateTime deadlineUtc = DateTime.UtcNow.Add(timeout);
|
||
Exception lastError = null;
|
||
|
||
while (DateTime.UtcNow < deadlineUtc)
|
||
{
|
||
try
|
||
{
|
||
JObject pingResponse = await GetJsonAsync("/api/test/ping").ConfigureAwait(false);
|
||
if (pingResponse.Value<bool?>("ok") == true)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
lastError = ex;
|
||
}
|
||
|
||
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
||
}
|
||
|
||
Assert.Fail(
|
||
"Navisworks 测试服务未就绪。请先用 start-navisworks.bat 启动 Navisworks,并确保插件面板已加载。最后错误: {0}",
|
||
lastError?.Message ?? "unknown");
|
||
}
|
||
|
||
public async Task<JObject> RunVirtualCollisionTestAsync(string pathType, int timeoutSeconds)
|
||
{
|
||
return await RunVirtualCollisionTestCoreAsync(null, pathType, timeoutSeconds).ConfigureAwait(false);
|
||
}
|
||
|
||
public async Task<JObject> RunVirtualCollisionTestAsync(string routeName, string pathType, int timeoutSeconds)
|
||
{
|
||
return await RunVirtualCollisionTestCoreAsync(routeName, pathType, timeoutSeconds).ConfigureAwait(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 运行碰撞测试(虚拟或真实物体)。
|
||
/// useVirtualObject=false 时必须提供 animatedObjectPath(ModelPath 定位,格式如 0/0/769/0)。
|
||
/// </summary>
|
||
public async Task<JObject> RunCollisionTestAsync(
|
||
string pathType,
|
||
int timeoutSeconds,
|
||
string routeName = null,
|
||
bool useVirtualObject = true,
|
||
string animatedObjectPath = null)
|
||
{
|
||
var queryParts = new System.Collections.Generic.List<string>
|
||
{
|
||
"pathType=" + Uri.EscapeDataString(pathType),
|
||
"timeoutSeconds=" + timeoutSeconds.ToString(CultureInfo.InvariantCulture),
|
||
"useVirtualObject=" + (useVirtualObject ? "true" : "false")
|
||
};
|
||
|
||
if (!string.IsNullOrWhiteSpace(routeName))
|
||
{
|
||
queryParts.Add("routeName=" + Uri.EscapeDataString(routeName));
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(animatedObjectPath))
|
||
{
|
||
queryParts.Add("animatedObjectPath=" + Uri.EscapeDataString(animatedObjectPath));
|
||
}
|
||
|
||
string requestUri = "/api/test/run-virtual-collision-test?" + string.Join("&", queryParts);
|
||
return await PostJsonAsync(requestUri).ConfigureAwait(false);
|
||
}
|
||
|
||
private async Task<JObject> RunVirtualCollisionTestCoreAsync(string routeName, string pathType, int timeoutSeconds)
|
||
{
|
||
string requestUri = string.Format(
|
||
CultureInfo.InvariantCulture,
|
||
"/api/test/run-virtual-collision-test?routeName={0}&pathType={1}&timeoutSeconds={2}",
|
||
Uri.EscapeDataString(routeName ?? string.Empty),
|
||
Uri.EscapeDataString(pathType),
|
||
timeoutSeconds);
|
||
|
||
return await PostJsonAsync(requestUri).ConfigureAwait(false);
|
||
}
|
||
|
||
public async Task<JObject> ImportRouteFileAsync(string filePath, bool overwrite = false)
|
||
{
|
||
string requestUri = string.Format(
|
||
"/api/test/import-route-file?path={0}&overwrite={1}",
|
||
Uri.EscapeDataString(filePath),
|
||
overwrite ? "true" : "false");
|
||
|
||
return await PostJsonAsync(requestUri).ConfigureAwait(false);
|
||
}
|
||
|
||
public async Task<JObject> SelectRouteAsync(string routeName)
|
||
{
|
||
string requestUri = string.Format(
|
||
"/api/test/select-route?name={0}",
|
||
Uri.EscapeDataString(routeName));
|
||
|
||
return await PostJsonAsync(requestUri).ConfigureAwait(false);
|
||
}
|
||
|
||
public async Task<JObject> AnalyzeAutoPathGridAsync(string pathType)
|
||
{
|
||
string requestUri = string.Format(
|
||
"/api/test/analyze-auto-path-grid?pathType={0}",
|
||
Uri.EscapeDataString(pathType));
|
||
|
||
return await PostJsonAsync(requestUri).ConfigureAwait(false);
|
||
}
|
||
|
||
public async Task<JObject> RunAutoPathAsync(
|
||
string pathType,
|
||
string routeName,
|
||
double objectLengthInMeters,
|
||
double objectWidthInMeters,
|
||
double safetyMarginInMeters,
|
||
double gridSizeInMeters)
|
||
{
|
||
string requestUri = string.Format(
|
||
CultureInfo.InvariantCulture,
|
||
"/api/test/run-auto-path?pathType={0}&routeName={1}&objectLengthInMeters={2}&objectWidthInMeters={3}&safetyMarginInMeters={4}&gridSizeInMeters={5}",
|
||
Uri.EscapeDataString(pathType),
|
||
Uri.EscapeDataString(routeName),
|
||
objectLengthInMeters,
|
||
objectWidthInMeters,
|
||
safetyMarginInMeters,
|
||
gridSizeInMeters);
|
||
|
||
return await PostJsonAsync(requestUri).ConfigureAwait(false);
|
||
}
|
||
|
||
private async Task<JObject> GetJsonAsync(string requestUri)
|
||
{
|
||
using (HttpResponseMessage response = await _httpClient.GetAsync(requestUri).ConfigureAwait(false))
|
||
{
|
||
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||
response.EnsureSuccessStatusCode();
|
||
return JObject.Parse(content);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动画逐帧状态探针:生成动画后 seek 到指定进度点,返回各点物体跟踪位置/yaw。
|
||
/// </summary>
|
||
public async Task<JObject> ProbeAnimationFramesAsync(
|
||
string pathType,
|
||
int timeoutSeconds,
|
||
string probeProgress = "0,0.25,0.5,0.75,1.0")
|
||
{
|
||
string requestUri = string.Format(
|
||
CultureInfo.InvariantCulture,
|
||
"/api/test/probe-animation-frames?pathType={0}&probeProgress={1}&timeoutSeconds={2}",
|
||
Uri.EscapeDataString(pathType),
|
||
Uri.EscapeDataString(probeProgress),
|
||
timeoutSeconds);
|
||
|
||
return await PostJsonAsync(requestUri).ConfigureAwait(false);
|
||
}
|
||
|
||
private async Task<JObject> PostJsonAsync(string requestUri)
|
||
{
|
||
using (HttpResponseMessage response = await _httpClient.PostAsync(requestUri, new StringContent(string.Empty)).ConfigureAwait(false))
|
||
{
|
||
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||
response.EnsureSuccessStatusCode();
|
||
return JObject.Parse(content);
|
||
}
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
_httpClient.Dispose();
|
||
}
|
||
}
|
||
}
|