diff --git a/src/Core/Collision/ClashDetectiveIntegration.cs b/src/Core/Collision/ClashDetectiveIntegration.cs index 9935ac0..41f9148 100644 --- a/src/Core/Collision/ClashDetectiveIntegration.cs +++ b/src/Core/Collision/ClashDetectiveIntegration.cs @@ -470,9 +470,21 @@ namespace NavisworksTransport } } + // 🔥 PathId 缺失/解析失败(F1 副实例映射未命中场景):按 DisplayName 在主模型查找(唯一匹配) + if (collidedObject == null && !string.IsNullOrWhiteSpace(obj.ObjectName)) + { + collidedObject = TryFindSingleObjectByName(obj.ObjectName); + if (collidedObject != null) + { + LogManager.Info($"[LoadClashDetectiveResultsFromDatabase] 已按名称匹配碰撞对象: {obj.ObjectName}"); + } + } + if (collidedObject == null) { - throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 无法通过路径找到物体对象: {obj.ModelIndex},{obj.PathId}"); + // 🔥 容错:单个对象无法定位不阻断整个报告——跳过并警告(其余对象仍可显示) + LogManager.Warning($"[LoadClashDetectiveResultsFromDatabase] 跳过无法定位的碰撞对象: {obj.ObjectName} (ModelIndex={obj.ModelIndex}, PathId={obj.PathId})"); + continue; } if (!ModelItemAnalysisHelper.IsModelItemValid(collidedObject)) @@ -1300,6 +1312,50 @@ namespace NavisworksTransport } } + /// + /// 按 DisplayName 在模型树中查找唯一匹配对象(F1 副实例对象映射回主模型用)。 + /// 多匹配(重名)返回 null,避免误配。 + /// + private static ModelItem TryFindSingleObjectByName(string displayName) + { + try + { + var document = Application.ActiveDocument; + if (document == null || document.IsClear) + { + return null; + } + + ModelItem found = null; + foreach (var model in document.Models) + { + if (model.RootItem == null) + { + continue; + } + + foreach (var item in model.RootItem.DescendantsAndSelf) + { + if (string.Equals(item.DisplayName, displayName, StringComparison.OrdinalIgnoreCase)) + { + if (found != null) + { + return null; // 重名 → 不匹配 + } + + found = item; + } + } + } + + return found; + } + catch + { + return null; + } + } + /// /// 显示碰撞检测取消提示消息 /// diff --git a/src/Core/Services/SectionBoxBatchDetector.cs b/src/Core/Services/SectionBoxBatchDetector.cs index 19707ed..5910c54 100644 --- a/src/Core/Services/SectionBoxBatchDetector.cs +++ b/src/Core/Services/SectionBoxBatchDetector.cs @@ -197,21 +197,69 @@ namespace NavisworksTransport.Core.Services } // F2:记录盒内对象映射(层级链去根 → 主模型 PathId) + // 🔥 覆盖盒内对象的完整子孙树 + 祖先链(嵌套实例容器层): + // 副实例碰撞对象可能是盒内几何对象的祖先(IsComposite 嵌套实例容器, + // 如 Revit 门/窗实例——不在 ObjectsInBox 但可被碰撞), + // 也可能是其嵌套子节点——三者都必须建映射 foreach (ModelItem boxItem in traversal.ObjectsInBox) { - string chain = BuildAncestorChain(boxItem); - if (string.IsNullOrWhiteSpace(chain)) - { - continue; - } - + // 1) 子孙树(含自身) + IEnumerable nodesToMap; try { - var path = document.Models.CreatePathId(boxItem); - chainToMainPathId[TrimRootFromChain(chain)] = new MainItemPath(path.ModelIndex, path.PathId); + nodesToMap = boxItem.DescendantsAndSelf; } catch { + nodesToMap = new[] { boxItem }; + } + + foreach (ModelItem node in nodesToMap) + { + if (node == null || string.IsNullOrWhiteSpace(node.DisplayName)) + { + continue; + } + + string chain = BuildAncestorChain(node); + if (string.IsNullOrWhiteSpace(chain)) + { + continue; + } + + try + { + var path = document.Models.CreatePathId(node); + chainToMainPathId[TrimRootFromChain(chain)] = new MainItemPath(path.ModelIndex, path.PathId); + } + catch + { + } + } + + // 2) 祖先链(向上到模型根)——嵌套实例容器节点(IsComposite 无自身几何) + var ancestor = boxItem.Parent; + int ancestorGuard = 0; + while (ancestor != null && ancestorGuard < 15) + { + if (!string.IsNullOrWhiteSpace(ancestor.DisplayName)) + { + string ancestorChain = BuildAncestorChain(ancestor); + if (!string.IsNullOrWhiteSpace(ancestorChain)) + { + try + { + var path = document.Models.CreatePathId(ancestor); + chainToMainPathId[TrimRootFromChain(ancestorChain)] = new MainItemPath(path.ModelIndex, path.PathId); + } + catch + { + } + } + } + + ancestor = ancestor.Parent; + ancestorGuard++; } } LogManager.Info($"[剖面盒批检测] 盒内对象映射: {chainToMainPathId.Count} 条"); @@ -679,6 +727,35 @@ namespace NavisworksTransport.Core.Services mapping.MainPathId = tailMatched.PathId; mapping.MainModelIndex = tailMatched.ModelIndex; } + else + { + // 🔥 映射诊断:打印碰撞对象链与盒内映射候选,定位匹配失败原因 + string last3 = string.Join(" / ", tail.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.Trim()).Reverse().Take(3).Reverse()); + var nearCandidates = chainToMainPathId.Keys + .Where(k => k.EndsWith(last3, StringComparison.OrdinalIgnoreCase)) + .Take(3) + .ToList(); + var nameCandidates = chainToMainPathId.Keys + .Where(k => k.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0) + .Take(3) + .ToList(); + // 等长精确候选:与副链完全相等(Trim 后)的字典 key——验证隐藏字符差异 + string tailTrimmed = string.Join(" /", tail.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim())); + var equalCandidates = chainToMainPathId.Keys + .Where(k => + { + string kTrimmed = string.Join(" /", k.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim())); + return string.Equals(kTrimmed, tailTrimmed, StringComparison.OrdinalIgnoreCase); + }) + .Take(3) + .ToList(); + LogManager.Warning($"[F2映射诊断] {name}: 副链去根=[{tail}] 等长候选={equalCandidates.Count} 例: {string.Join(" || ", equalCandidates)} | 末3候选={nearCandidates.Count} | 名候选={nameCandidates.Count}"); + } + } + else + { + LogManager.Warning($"[F2映射诊断] {name}: 链为空或字典为空, chain=[{chain}]"); } } diff --git a/src/Core/Services/TestAutomationHttpService.cs b/src/Core/Services/TestAutomationHttpService.cs index a9496af..71c8fe7 100644 --- a/src/Core/Services/TestAutomationHttpService.cs +++ b/src/Core/Services/TestAutomationHttpService.cs @@ -266,6 +266,14 @@ namespace NavisworksTransport.Core.Services return; } + if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) && + string.Equals(request.Path, "/api/test/tree-dump", StringComparison.OrdinalIgnoreCase)) + { + object payload = InvokeOnUiThread(() => BuildTreeDumpPayload(request.Query)); + await WriteJsonResponseAsync(writer, 200, BuildEnvelope(true, payload)).ConfigureAwait(false); + return; + } + if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) && string.Equals(request.Path, "/api/test/traversable-items", StringComparison.OrdinalIgnoreCase)) { @@ -582,6 +590,85 @@ namespace NavisworksTransport.Core.Services }; } + private object BuildTreeDumpPayload(Dictionary query) + { + // 【实验端点】模型树结构对比(主 NW vs 副实例导出模型) + // 支持 ?name=xxx:返回匹配对象的完整层级链(对比主/副实例同一对象) + Document doc = Autodesk.Navisworks.Api.Application.ActiveDocument; + if (doc == null || doc.IsClear) + { + return new { error = "无活动文档" }; + } + + string matchName = GetOptionalQueryValue(query, "name"); + if (!string.IsNullOrWhiteSpace(matchName)) + { + var chains = new List(); + int matchCount = 0; + foreach (var model in doc.Models) + { + if (model.RootItem == null) + { + continue; + } + + foreach (var item in model.RootItem.DescendantsAndSelf) + { + if (string.Equals(item.DisplayName, matchName, StringComparison.OrdinalIgnoreCase)) + { + matchCount++; + chains.Add(BuildAncestorNameChain(item)); + if (chains.Count >= 5) + { + break; + } + } + } + } + + return new + { + documentName = doc.FileName, + modelCount = doc.Models.Count, + nameMatch = matchName, + matchCount, + chains + }; + } + + var models = new List(); + foreach (var model in doc.Models) + { + var root = model.RootItem; + var topChildren = new List(); + if (root?.Children != null) + { + foreach (var child in root.Children) + { + topChildren.Add(child.DisplayName ?? ""); + if (topChildren.Count >= 8) + { + break; + } + } + } + + models.Add(new + { + modelName = root?.DisplayName ?? "", + topChildren, + totalModels = doc.Models.Count + }); + } + + return new + { + documentName = doc.FileName, + modelCount = doc.Models.Count, + models + }; + } + private object BuildTraversableItemsPayload() { // 【实验端点】查询当前文档的可通行物流属性对象(验证通道属性是否在模型文件中)