diff --git a/src/Core/BatchQueueManager.cs b/src/Core/BatchQueueManager.cs
index c7d7ff7..980d626 100644
--- a/src/Core/BatchQueueManager.cs
+++ b/src/Core/BatchQueueManager.cs
@@ -501,6 +501,18 @@ namespace NavisworksTransport.Core
LogManager.Info(
$"[批处理] 剖面盒自动检测完成: {pathRoute.Name}, 碰撞={detection.TotalCollisions}, " +
$"唯一对象={detection.UniqueCollidedObjectsCount}, 对象=[{string.Join(", ", detection.CollidedObjects)}]");
+
+ // F2:碰撞对象映射(副实例 → 主模型 PathId,按层级链匹配)
+ int mappedCount = 0;
+ foreach (var mapping in detection.CollidedObjectMappings)
+ {
+ if (!string.IsNullOrWhiteSpace(mapping.MainPathId))
+ {
+ mappedCount++;
+ LogManager.Info($"[F2映射] {mapping.Name}: 副实例={mapping.SecondaryPathId} -> 主模型={mapping.MainPathId}");
+ }
+ }
+ LogManager.Info($"[F2映射] 完成: {mappedCount}/{detection.CollidedObjectMappings.Count} 个碰撞对象映射到主模型");
return;
}
diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs
index 6fd036c..b01b37a 100644
--- a/src/Core/PathPlanningManager.cs
+++ b/src/Core/PathPlanningManager.cs
@@ -91,6 +91,11 @@ namespace NavisworksTransport
///
public bool IsToolPluginActive => _isToolPluginActive;
+ ///
+ /// 路径数据库是否已初始化(F1 副实例检测前等待数据库就绪,避免路径导入时数据库未建导致丢失)。
+ ///
+ public bool IsDatabaseReady => _pathDatabase != null;
+
///
/// 获取当前是否在预览模式
///
diff --git a/src/Core/Services/SectionBoxBatchDetector.cs b/src/Core/Services/SectionBoxBatchDetector.cs
index 17baef5..e9e5b2e 100644
--- a/src/Core/Services/SectionBoxBatchDetector.cs
+++ b/src/Core/Services/SectionBoxBatchDetector.cs
@@ -50,6 +50,21 @@ namespace NavisworksTransport.Core.Services
public int TotalFrames { get; set; }
public string LocalModelPath { get; set; }
public string ErrorMessage { get; set; }
+
+ ///
+ /// F2:碰撞对象映射(名称 → 主模型 PathId,按层级链匹配)
+ ///
+ public List CollidedObjectMappings { get; set; } = new List();
+ }
+
+ ///
+ /// F2:碰撞对象映射(副实例 → 主模型)。
+ ///
+ public class CollidedObjectMapping
+ {
+ public string Name { get; set; }
+ public string SecondaryPathId { get; set; }
+ public string MainPathId { get; set; }
}
private const string DbPathEnvironmentVariable = "TRANSPORTPLUGIN_TEST_DB_PATH";
@@ -106,8 +121,9 @@ namespace NavisworksTransport.Core.Services
BoundingBox3D bounds = SectionClipHelper.CalculatePathClipBox(adjustedPoints, 4.0, 2.0);
- // 2. 导出局部 NWD(UI 线程:NW API + 导出)
+ // 2. 导出局部 NWD(UI 线程:NW API + 导出);同时构建盒内对象映射(chain→主PathId,F2 用)
bool exported = false;
+ var chainToMainPathId = new Dictionary(StringComparer.OrdinalIgnoreCase);
try
{
exported = System.Windows.Application.Current.Dispatcher.Invoke(() =>
@@ -120,6 +136,26 @@ namespace NavisworksTransport.Core.Services
return false;
}
+ // F2:记录盒内对象映射(层级链去根 → 主模型 PathId)
+ foreach (ModelItem boxItem in traversal.ObjectsInBox)
+ {
+ string chain = BuildAncestorChain(boxItem);
+ if (string.IsNullOrWhiteSpace(chain))
+ {
+ continue;
+ }
+
+ try
+ {
+ var path = document.Models.CreatePathId(boxItem);
+ chainToMainPathId[TrimRootFromChain(chain)] = path.PathId;
+ }
+ catch
+ {
+ }
+ }
+ LogManager.Info($"[剖面盒批检测] 盒内对象映射: {chainToMainPathId.Count} 条");
+
string exportedPath = exporter.ExportToNwd(document, traversal, localModelPath);
return !string.IsNullOrEmpty(exportedPath) && File.Exists(exportedPath);
});
@@ -151,6 +187,13 @@ namespace NavisworksTransport.Core.Services
return result;
}
+ // 等待副实例路径数据库就绪(数据库初始化晚于插件加载,路径导入前必须先建库,否则路径丢失)
+ if (!await WaitDatabaseReadyAsync(client, TimeSpan.FromSeconds(60)).ConfigureAwait(false))
+ {
+ result.ErrorMessage = "副实例路径数据库未就绪(60s 超时)";
+ return result;
+ }
+
if (!await ImportRouteAsync(client, routeFilePath).ConfigureAwait(false))
{
result.ErrorMessage = "副实例导入路径失败";
@@ -173,7 +216,8 @@ namespace NavisworksTransport.Core.Services
return await RunDetectionAsync(
client, route, isVirtualObject, animatedObjectPath,
virtualObjectLengthModelUnits, virtualObjectWidthModelUnits,
- virtualObjectHeightModelUnits, frameRate, durationSeconds, result).ConfigureAwait(false);
+ virtualObjectHeightModelUnits, frameRate, durationSeconds, result,
+ chainToMainPathId).ConfigureAwait(false);
}
finally
{
@@ -298,6 +342,41 @@ namespace NavisworksTransport.Core.Services
return false;
}
+ ///
+ /// 等待副实例路径数据库就绪(PathPlanningManager.IsDatabaseReady)。
+ /// 副实例数据库初始化晚于插件加载,路径导入前必须已建库。
+ ///
+ private static async Task WaitDatabaseReadyAsync(HttpClient client, TimeSpan timeout)
+ {
+ DateTime deadline = DateTime.UtcNow.Add(timeout);
+ while (DateTime.UtcNow < deadline)
+ {
+ try
+ {
+ using (var response = await client.GetAsync("/api/test/status").ConfigureAwait(false))
+ {
+ if (response.IsSuccessStatusCode)
+ {
+ string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ JObject payload = JObject.Parse(content);
+ if (payload.Value("ok") == true &&
+ payload["data"]?["pathManager"]?["isDatabaseReady"]?.Value() == true)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ catch
+ {
+ }
+
+ await Task.Delay(1000).ConfigureAwait(false);
+ }
+
+ return false;
+ }
+
private static async Task ImportRouteAsync(HttpClient client, string routeFilePath)
{
string requestUri = "/api/test/import-route-file?path=" + Uri.EscapeDataString(routeFilePath) + "&overwrite=true";
@@ -442,6 +521,37 @@ namespace NavisworksTransport.Core.Services
return string.Join(" / ", segments.Skip(1).Select(s => s.Trim()));
}
+ ///
+ /// 构造 ModelItem 的层级链(AncestorsAndSelf 的 DisplayName,'/' 分隔)。
+ ///
+ private static string BuildAncestorChain(ModelItem item)
+ {
+ try
+ {
+ var segments = new List();
+ var current = item;
+ int guard = 0;
+
+ while (current != null && guard < 15)
+ {
+ var name = current.DisplayName;
+ if (!string.IsNullOrWhiteSpace(name))
+ {
+ segments.Add(name.Trim());
+ }
+ current = current.Parent;
+ guard++;
+ }
+
+ segments.Reverse();
+ return string.Join(" / ", segments);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
private static async Task RunDetectionAsync(
HttpClient client,
PathRoute route,
@@ -452,7 +562,8 @@ namespace NavisworksTransport.Core.Services
double objectHeightModelUnits,
int frameRate,
double durationSeconds,
- DetectionResult result)
+ DetectionResult result,
+ Dictionary chainToMainPathId)
{
// 动画参数从模型单位换算回米(副实例 config 以米存储)
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
@@ -501,7 +612,35 @@ namespace NavisworksTransport.Core.Services
JArray collided = (JArray)report["collidedObjects"];
if (collided != null)
{
- result.CollidedObjects = collided.Select(t => (string)t).ToList();
+ foreach (JToken token in collided)
+ {
+ JObject obj = (JObject)token;
+ string name = (string)obj["name"];
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ continue;
+ }
+
+ var mapping = new CollidedObjectMapping
+ {
+ Name = name,
+ SecondaryPathId = (string)obj["pathId"]
+ };
+
+ // F2:按层级链(去根)查盒内对象映射 → 主模型 PathId
+ string chain = (string)obj["ancestorChain"];
+ if (!string.IsNullOrWhiteSpace(chain))
+ {
+ string tail = TrimRootFromChain(chain);
+ if (tail != null && chainToMainPathId != null && chainToMainPathId.TryGetValue(tail, out string mainPathId))
+ {
+ mapping.MainPathId = mainPathId;
+ }
+ }
+
+ result.CollidedObjects.Add(name);
+ result.CollidedObjectMappings.Add(mapping);
+ }
}
}
diff --git a/src/Core/Services/TestAutomationHttpService.cs b/src/Core/Services/TestAutomationHttpService.cs
index 5076e67..c9293c3 100644
--- a/src/Core/Services/TestAutomationHttpService.cs
+++ b/src/Core/Services/TestAutomationHttpService.cs
@@ -477,6 +477,7 @@ namespace NavisworksTransport.Core.Services
pathManager = new
{
isAvailable = pathManager != null,
+ isDatabaseReady = pathManager != null && pathManager.IsDatabaseReady,
routeCount = allRoutes?.Count ?? 0,
currentRouteId = currentRoute?.Id,
currentRouteName = currentRoute?.Name,
@@ -1151,6 +1152,70 @@ namespace NavisworksTransport.Core.Services
return port;
}
+ ///
+ /// F2:序列化碰撞对象详情(name + 副实例 PathId + 层级链),
+ /// 供主 NW 按层级链(导出保留树结构,去根一致)映射回主模型。
+ ///
+ private static List