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 SerializeCollidedObjects( + List allCollisions, List nameList) + { + var result = new List(); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (allCollisions != null) + { + foreach (var collision in allCollisions) + { + if (collision?.Item2 == null) + { + continue; + } + + string name = collision.Item2.DisplayName; + if (string.IsNullOrWhiteSpace(name) || !seen.Add(name)) + { + continue; + } + + if (result.Count >= 50) + { + break; + } + + string pathId = null; + try + { + var path = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(collision.Item2); + pathId = path.PathId; + } + catch + { + } + + result.Add(new + { + name, + pathId, + ancestorChain = BuildAncestorNameChain(collision.Item2) + }); + } + } + + // fallback:AllCollisions 无 Item2 引用时退回名字列表 + if (result.Count == 0 && nameList != null) + { + foreach (string name in nameList.Take(50)) + { + if (seen.Add(name)) + { + result.Add(new { name, pathId = (string)null, ancestorChain = (string)null }); + } + } + } + + return result; + } + private object BuildSelectionPayload() { Document activeDocument = Autodesk.Navisworks.Api.Application.ActiveDocument; @@ -1361,10 +1426,8 @@ namespace NavisworksTransport.Core.Services screenshotCount = report.Screenshots?.Count ?? 0, resultId = report.ResultId, routeId = report.RouteId, - // E4:碰撞对象名字列表(用于跨实例报告回传;PathId/位置映射属 F2 阶段) - collidedObjects = report.CollidedObjectsList?.Count > 0 - ? report.CollidedObjectsList.Take(50).ToList() - : new List() + // F2:碰撞对象详情(name + 副实例 PathId + 层级链),用于跨实例报告映射回主模型 + collidedObjects = SerializeCollidedObjects(report.AllCollisions, report.CollidedObjectsList) } }; }); diff --git a/src/Utils/UnitsConverter.cs b/src/Utils/UnitsConverter.cs index 751600c..7ab3463 100644 --- a/src/Utils/UnitsConverter.cs +++ b/src/Utils/UnitsConverter.cs @@ -62,10 +62,25 @@ namespace NavisworksTransport.Utils _cachedMetersToUnitsFactor = GetMetersToUnitsConversionFactor(currentUnits); } } - catch (Exception) + catch { - // 出错时保留上次的有效值(如果有),否则保持 NaN 以暴露错误 - // 不要设置默认值,避免掩盖问题 + // 主路径异常时,用显式文档单位兜底(副实例/文档加载瞬间 ActiveDocument.Units 访问可能抛, + // 显式访问通常可用;避免缓存长期 NaN 导致尺寸/路径长度全错) + try + { + var doc = Application.ActiveDocument; + if (doc != null) + { + var currentUnits = doc.Units; + _cachedUnits = currentUnits; + _cachedUnitsToMetersFactor = GetUnitsToMetersConversionFactor(currentUnits); + _cachedMetersToUnitsFactor = GetMetersToUnitsConversionFactor(currentUnits); + } + } + catch + { + // 仍失败则保留上次有效值(或 NaN 暴露错误) + } } } @@ -76,6 +91,17 @@ namespace NavisworksTransport.Utils public static double GetUnitsToMetersConversionFactor() { UpdateCache(); + + // 缓存异常(NaN,副实例/文档加载瞬间)时用显式文档单位兜底 + if (double.IsNaN(_cachedUnitsToMetersFactor)) + { + var doc = Application.ActiveDocument; + if (doc != null) + { + return GetUnitsToMetersConversionFactor(doc.Units); + } + } + return _cachedUnitsToMetersFactor; } @@ -116,12 +142,24 @@ namespace NavisworksTransport.Utils } /// - /// 获取米转换为Navisworks文档单位的系数 + /// + /// 获取米转换为文档单位的系数 /// /// 转换系数(米转换为文档单位) public static double GetMetersToUnitsConversionFactor() { UpdateCache(); + + // 缓存异常(NaN,副实例/文档加载瞬间)时用显式文档单位兜底 + if (double.IsNaN(_cachedMetersToUnitsFactor)) + { + var doc = Application.ActiveDocument; + if (doc != null) + { + return GetMetersToUnitsConversionFactor(doc.Units); + } + } + return _cachedMetersToUnitsFactor; }