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:
parent
54255516fd
commit
68b75b5941
@ -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)
|
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))
|
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>
|
||||||
/// 显示碰撞检测取消提示消息
|
/// 显示碰撞检测取消提示消息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -197,21 +197,69 @@ namespace NavisworksTransport.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// F2:记录盒内对象映射(层级链去根 → 主模型 PathId)
|
// F2:记录盒内对象映射(层级链去根 → 主模型 PathId)
|
||||||
|
// 🔥 覆盖盒内对象的完整子孙树 + 祖先链(嵌套实例容器层):
|
||||||
|
// 副实例碰撞对象可能是盒内几何对象的祖先(IsComposite 嵌套实例容器,
|
||||||
|
// 如 Revit 门/窗实例——不在 ObjectsInBox 但可被碰撞),
|
||||||
|
// 也可能是其嵌套子节点——三者都必须建映射
|
||||||
foreach (ModelItem boxItem in traversal.ObjectsInBox)
|
foreach (ModelItem boxItem in traversal.ObjectsInBox)
|
||||||
{
|
{
|
||||||
string chain = BuildAncestorChain(boxItem);
|
// 1) 子孙树(含自身)
|
||||||
if (string.IsNullOrWhiteSpace(chain))
|
IEnumerable<ModelItem> nodesToMap;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var path = document.Models.CreatePathId(boxItem);
|
nodesToMap = boxItem.DescendantsAndSelf;
|
||||||
chainToMainPathId[TrimRootFromChain(chain)] = new MainItemPath(path.ModelIndex, path.PathId);
|
|
||||||
}
|
}
|
||||||
catch
|
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} 条");
|
LogManager.Info($"[剖面盒批检测] 盒内对象映射: {chainToMainPathId.Count} 条");
|
||||||
@ -679,6 +727,35 @@ namespace NavisworksTransport.Core.Services
|
|||||||
mapping.MainPathId = tailMatched.PathId;
|
mapping.MainPathId = tailMatched.PathId;
|
||||||
mapping.MainModelIndex = tailMatched.ModelIndex;
|
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}]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -266,6 +266,14 @@ namespace NavisworksTransport.Core.Services
|
|||||||
return;
|
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) &&
|
if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase) &&
|
||||||
string.Equals(request.Path, "/api/test/traversable-items", 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()
|
private object BuildTraversableItemsPayload()
|
||||||
{
|
{
|
||||||
// 【实验端点】查询当前文档的可通行物流属性对象(验证通道属性是否在模型文件中)
|
// 【实验端点】查询当前文档的可通行物流属性对象(验证通道属性是否在模型文件中)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user