- 服务端 run-virtual-collision-test 支持 useVirtualObject=false + animatedObjectPath(ModelPath 定位,解决 DisplayName 重名) - ResolveAnimatedObjectFromQuery 支持按 CreatePathId 的 PathId 精确匹配 - 修复 ModelItem 身份校验:ReferenceEquals 对 NW API 同一逻辑对象不同实例不可靠,改用 InstanceGuid 比较 - 新增 RealObjectAnimationAutomationTests:Ground/Hoisting/Rail 真实物体动画完整流程 (42" Diameter=0/0/660/0,25" x 25"=0/0/769/0) - 集成测试 9/9 通过(3 虚拟碰撞 + 1 autoPath + 2 导入 + 3 真实物体)
180 lines
6.8 KiB
C#
180 lines
6.8 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);
|
||
}
|
||
}
|
||
|
||
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();
|
||
}
|
||
}
|
||
}
|