From ac454196f1d148d951f0b4ece6cacda0bfb313e8 Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Tue, 4 Aug 2026 18:44:01 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=E8=A1=A5=20T16=20=E5=89=96=E9=9D=A2=E7=9B=92=E5=AF=BC=E5=87=BA?=
=?UTF-8?q?=EF=BC=8825/25=20=E9=80=9A=E8=BF=87=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 服务端新增 export-section-box 端点:mode=bbox(指定包围盒,测试可控)/ mode=activeView(UI 剖面盒)
- 新增 SectionBoxExportAutomationTests:导出 NWD 文件存在/对象数/文件大小/隐藏节点断言
- 客户端 ExportSectionBoxAsync 方法
---
NavisworksTransport.UnitTests.csproj | 1 +
.../NavisworksTestAutomationClient.cs | 17 ++++
.../SectionBoxExportAutomationTests.cs | 83 +++++++++++++++++++
...26-08-02-integration-test-coverage-plan.md | 3 +
.../Services/TestAutomationHttpService.cs | 83 +++++++++++++++++++
5 files changed, 187 insertions(+)
create mode 100644 UnitTests/Integration/SectionBoxExportAutomationTests.cs
diff --git a/NavisworksTransport.UnitTests.csproj b/NavisworksTransport.UnitTests.csproj
index ab35c52..7068f09 100644
--- a/NavisworksTransport.UnitTests.csproj
+++ b/NavisworksTransport.UnitTests.csproj
@@ -84,6 +84,7 @@
+
diff --git a/UnitTests/Integration/NavisworksTestAutomationClient.cs b/UnitTests/Integration/NavisworksTestAutomationClient.cs
index fe813c2..ef1392a 100644
--- a/UnitTests/Integration/NavisworksTestAutomationClient.cs
+++ b/UnitTests/Integration/NavisworksTestAutomationClient.cs
@@ -293,6 +293,23 @@ namespace NavisworksTransport.UnitTests.Integration
return await PostJsonAsync(requestUri, 60).ConfigureAwait(false);
}
+ ///
+ /// 剖面盒导出(指定包围盒模式),返回导出文件信息。
+ ///
+ public async Task ExportSectionBoxAsync(
+ string filePath,
+ double x1, double y1, double z1,
+ double x2, double y2, double z2)
+ {
+ string requestUri = string.Format(
+ CultureInfo.InvariantCulture,
+ "/api/test/export-section-box?mode=bbox&x1={0}&y1={1}&z1={2}&x2={3}&y2={4}&z2={5}&path={6}",
+ x1, y1, z1, x2, y2, z2,
+ Uri.EscapeDataString(filePath));
+
+ return await PostJsonAsync(requestUri, 120).ConfigureAwait(false);
+ }
+
private async Task GetJsonAsync(string requestUri, int timeoutSeconds = 30)
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
diff --git a/UnitTests/Integration/SectionBoxExportAutomationTests.cs b/UnitTests/Integration/SectionBoxExportAutomationTests.cs
new file mode 100644
index 0000000..3401c8c
--- /dev/null
+++ b/UnitTests/Integration/SectionBoxExportAutomationTests.cs
@@ -0,0 +1,83 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Newtonsoft.Json.Linq;
+
+namespace NavisworksTransport.UnitTests.Integration
+{
+ ///
+ /// 剖面盒导出集成测试(覆盖计划 T16)。
+ ///
+ /// 验证 SectionBoxExporter 的指定包围盒导出链路:
+ /// - 导出 NWD 文件成功(文件存在且非空)
+ /// - 包围盒内对象数 > 0、文件大小 > 0
+ /// - 隐藏节点计算完成(hiddenNodeCount >= 0)
+ ///
+ /// 使用指定包围盒模式(bbox),不依赖 UI 剖面盒状态;包围盒覆盖模型 Hoisting/Rail 区域。
+ ///
+ [TestClass]
+ [TestCategory("NavisworksIntegration")]
+ public class SectionBoxExportAutomationTests
+ {
+ // 模型单位:覆盖 Hoisting/Rail 区域的包围盒(已验证含 57 个对象)
+ private const double MinX = -240, MinY = 10, MinZ = -20;
+ private const double MaxX = -170, MaxY = 30, MaxZ = 25;
+
+ [TestMethod]
+ [Timeout(240000)]
+ public async Task SectionBoxBBoxExport_ProducesValidNwd()
+ {
+ using (var client = new NavisworksTestAutomationClient())
+ {
+ await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);
+
+ string tempDir = Path.Combine(Path.GetTempPath(), "NavisworksTransportIntegrationTests", Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(tempDir);
+ try
+ {
+ string exportPath = Path.Combine(tempDir, "sectionbox-export.nwd");
+
+ JObject response = await client.ExportSectionBoxAsync(
+ exportPath,
+ MinX, MinY, MinZ, MaxX, MaxY, MaxZ).ConfigureAwait(false);
+ Assert.IsTrue(response.Value("ok"), "剖面盒导出失败: " + (string)response["error"]);
+
+ JObject data = (JObject)response["data"];
+
+ // 文件已生成且非空
+ Assert.IsFalse(string.IsNullOrWhiteSpace((string)data["filePath"]), "导出文件路径为空");
+ Assert.IsTrue(File.Exists(exportPath), "导出文件不存在: " + exportPath);
+ Assert.IsTrue((long)data["fileSize"] > 0, "导出文件大小应大于 0");
+ Assert.IsTrue(new FileInfo(exportPath).Length > 0, "导出文件实际为空");
+
+ // 包围盒内对象与隐藏节点
+ Assert.IsTrue((int)data["objectCount"] > 0, "包围盒内应找到对象");
+ Assert.IsTrue((int)data["hiddenNodeCount"] >= 0, "隐藏节点数不能为负");
+
+ Assert.AreEqual("bbox", (string)data["mode"], "导出模式应为 bbox");
+ }
+ finally
+ {
+ SafeDelete(tempDir);
+ }
+ }
+ }
+
+ private static void SafeDelete(string directory)
+ {
+ if (!Directory.Exists(directory))
+ {
+ return;
+ }
+
+ try
+ {
+ Directory.Delete(directory, true);
+ }
+ catch
+ {
+ }
+ }
+ }
+}
diff --git a/doc/working/2026-08-02-integration-test-coverage-plan.md b/doc/working/2026-08-02-integration-test-coverage-plan.md
index f5b2231..f7ccfc5 100644
--- a/doc/working/2026-08-02-integration-test-coverage-plan.md
+++ b/doc/working/2026-08-02-integration-test-coverage-plan.md
@@ -54,6 +54,7 @@
- [x] **T13 动画播放控制**(2026-08-04,`AnimationPlaybackControlAutomationTests`)— prepare→start→pause 验证→resume→finished
- [x] **T14 批量任务队列**(2026-08-04,`BatchQueueAutomationTests`)— 队列添加/计数递增(轻量版,不执行批量动画)
- [x] **T15 路径分析**(2026-08-04,`PathAnalysisAutomationTests`)— 评分结构验证;顺带修复产品 bug(CollisionResults 表已废弃导致路径分析 no such table)
+- [x] **T16 剖面盒导出**(2026-08-04,`SectionBoxExportAutomationTests`)— 指定包围盒模式导出 NWD(对象数/文件大小/隐藏节点),不依赖 UI 剖面盒状态
## 4. 测试服务支撑能力
@@ -74,6 +75,7 @@
| animation-playback-control | ✅ 已实现 | 播放控制序列(T13) |
| batch-queue-add | ✅ 已实现 | 批量队列添加(T14) |
| analyze-path | ✅ 已实现 | 路径分析评分(T15) |
+| export-section-box | ✅ 已实现 | 剖面盒导出 NWD(bbox/activeView 模式,T16) |
| 其余待扩展 | — | 批量执行、TimeLiner 集成等后续迭代补充 |
## 5. 完成记录
@@ -88,3 +90,4 @@
| 2026-08-04 | `44214ba` | T05/T06 + 基础修复(Free 路径、报告断言、动态解析、串行锁、脚本、加速 15fps/5s) |
| 2026-08-04 | 待提交 | T07-T10(障碍绕行、多策略、网格诊断、持久化)+ GetDetectionRecordById + detection-record 端点;P0+P1 完成,18/18 |
| 2026-08-04 | 待提交 | T11-T15(路径编辑/导出/播放控制/批量队列/路径分析)+ 服务端 5 端点 + 产品 bug 修复(PathDatabase CollisionResults→CollisionReports、RemovePoint 链路);P0+P1+P2 全部完成,集成测试 24/24,完整集 74.6s |
+| 2026-08-04 | 待提交 | T16 剖面盒导出(SectionBoxExportAutomationTests + export-section-box 端点,bbox 模式),25/25 |
diff --git a/src/Core/Services/TestAutomationHttpService.cs b/src/Core/Services/TestAutomationHttpService.cs
index 6bed637..1571215 100644
--- a/src/Core/Services/TestAutomationHttpService.cs
+++ b/src/Core/Services/TestAutomationHttpService.cs
@@ -298,6 +298,14 @@ namespace NavisworksTransport.Core.Services
return;
}
+ if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(request.Path, "/api/test/export-section-box", StringComparison.OrdinalIgnoreCase))
+ {
+ object payload = InvokeOnUiThread(() => ExportSectionBoxPayload(request.Query));
+ await WriteJsonResponseAsync(writer, 200, BuildEnvelope(true, payload)).ConfigureAwait(false);
+ return;
+ }
+
if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase) &&
string.Equals(request.Path, "/api/test/export-debug-snapshot", StringComparison.OrdinalIgnoreCase))
{
@@ -697,6 +705,81 @@ namespace NavisworksTransport.Core.Services
///
/// 路径分析(效率/安全评分),集成测试验证分析链路与评分结构。
///
+ ///
+ /// 剖面盒导出(T16):mode=bbox 用指定包围盒导出 NWD(集成测试可控,不依赖 UI 剖面盒状态);
+ /// mode=activeView 用当前视图剖面盒(需 UI 已激活 Box 模式)。
+ ///
+ private static object ExportSectionBoxPayload(Dictionary query)
+ {
+ Document document = Autodesk.Navisworks.Api.Application.ActiveDocument;
+ if (document == null)
+ {
+ throw new InvalidOperationException("当前没有活动文档");
+ }
+
+ string mode = GetOptionalQueryValue(query, "mode") ?? "bbox";
+ string filePath = GetOptionalQueryValue(query, "path");
+ if (string.IsNullOrWhiteSpace(filePath))
+ {
+ filePath = Path.Combine(Path.GetTempPath(), $"sectionbox-export-{Guid.NewGuid():N}.nwd");
+ }
+
+ var exporter = new SectionBoxExporter();
+
+ if (string.Equals(mode, "bbox", StringComparison.OrdinalIgnoreCase))
+ {
+ double x1 = ParseRequiredDouble(query, "x1");
+ double y1 = ParseRequiredDouble(query, "y1");
+ double z1 = ParseRequiredDouble(query, "z1");
+ double x2 = ParseRequiredDouble(query, "x2");
+ double y2 = ParseRequiredDouble(query, "y2");
+ double z2 = ParseRequiredDouble(query, "z2");
+
+ var bounds = new BoundingBox3D(
+ new Point3D(Math.Min(x1, x2), Math.Min(y1, y2), Math.Min(z1, z2)),
+ new Point3D(Math.Max(x1, x2), Math.Max(y1, y2), Math.Max(z1, z2)));
+
+ var traversal = exporter.GetObjectsAndHiddenItems(document, bounds);
+ if (traversal.ObjectsInBox.Count == 0)
+ {
+ throw new InvalidOperationException("指定包围盒内没有找到对象");
+ }
+
+ string exportedPath = exporter.ExportToNwd(document, traversal, filePath);
+ if (string.IsNullOrEmpty(exportedPath) || !File.Exists(exportedPath))
+ {
+ throw new InvalidOperationException($"NWD 导出失败: {filePath}");
+ }
+
+ LogManager.Info($"[测试HTTP] 剖面盒导出完成(bbox): 对象={traversal.ObjectsInBox.Count}, 隐藏={traversal.ItemsToHide.Count}, 文件={exportedPath}");
+
+ return new
+ {
+ mode = "bbox",
+ filePath = exportedPath,
+ objectCount = traversal.ObjectsInBox.Count,
+ hiddenNodeCount = traversal.ItemsToHide.Count,
+ fileSize = new FileInfo(exportedPath).Length
+ };
+ }
+
+ // activeView 模式:依赖当前视图剖面盒
+ var result = exporter.ExportSectionBoxFromActiveView(document, filePath);
+ if (!result.Success)
+ {
+ throw new InvalidOperationException(result.ErrorMessage ?? "剖面盒导出失败");
+ }
+
+ return new
+ {
+ mode = "activeView",
+ filePath = result.FilePath,
+ objectCount = result.ObjectCount,
+ hiddenNodeCount = result.HiddenNodeCount,
+ fileSize = result.FileSize
+ };
+ }
+
///
/// 路径编辑集成测试端点(T11):action=remove-point/update-point/orthogonalize/remove-loops。
/// 直接操作临时测试路径,验证 PathPlanningManager 编辑链路。