fix: F2 映射覆盖盒内对象祖先链(嵌套实例容器碰撞对象映射 0/5 → 4/4)

根因(经树结构实验确认):
- 副实例碰撞对象可能是盒内几何对象的祖先节点(IsComposite 嵌套实例
  容器——如 Revit 门/窗实例——不在 ObjectsInBox 但可被碰撞)
- 盒内映射原只覆盖 ObjectsInBox(238 条)——碰撞对象链无匹配(近候选=0)

修复:盒内映射构建覆盖三类节点:
1) ObjectsInBox 自身 + DescendantsAndSelf(嵌套子节点)
2) 沿祖先链向上到模型根(嵌套实例容器层)
→ 映射 238 → 619 条;Factory 实测映射率 0/5 → 4/4,
落库明细全部带主模型 PathId(1/1/21/0/0/7/0 等,真正可定位)

诊断:F2映射诊断日志(等长/末3/名字候选对比,映射失败时定位用)

验证:集成测试 25/25
This commit is contained in:
tian 2026-08-06 22:46:23 +08:00
parent 54255516fd
commit 68b75b5941
3 changed files with 229 additions and 9 deletions

View File

@ -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
}
}
/// <summary>
/// 按 DisplayName 在模型树中查找唯一匹配对象F1 副实例对象映射回主模型用)。
/// 多匹配(重名)返回 null避免误配。
/// </summary>
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;
}
}
/// <summary>
/// 显示碰撞检测取消提示消息
/// </summary>

View File

@ -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<ModelItem> 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}]");
}
}

View File

@ -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<string, string> 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<string>();
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<object>();
foreach (var model in doc.Models)
{
var root = model.RootItem;
var topChildren = new List<string>();
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()
{
// 【实验端点】查询当前文档的可通行物流属性对象(验证通道属性是否在模型文件中)