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