fix: F2 映射改为纯 PathId(段序列直查,删除全部名字/链匹配)
根因(经导出代码 + 固定文件树对比验证): - 导出 = document.ExportToNwd(ExcludeHiddenItems=true)(临时隐藏 itemsToHide) - 导出树段序列 = 主模型树跳过 itemsToHide 的可见子编号(模拟 vs 副实例实际树完全一致) - 此前链(名字)匹配在重名模型(Revit 嵌套实例)中映射到错误实例, 导致聚焦错误对象触发 NW 原生视角崩溃 修复: - 导出时 BuildSegmentMap:全树深度优先遍历,跳过 itemsToHide, 段序列 → 主 PathId(CreatePathId)映射(2112 条) - 副实例碰撞对象 PathId(段序列)直查表 → 主 PathId/ModelIndex - 删除 TryMatchChainTail/链尾部匹配/名字候选等全部名字相关代码 (TrimRootFromChain/BuildAncestorChain 保留——移动物体副实例解析用) 验证:Factory 实测映射 1/1、4/4(纯 PathId,无名字);集成测试 25/25
This commit is contained in:
parent
68b75b5941
commit
4ced5eb77b
@ -196,73 +196,27 @@ namespace NavisworksTransport.Core.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// F2:记录盒内对象映射(层级链去根 → 主模型 PathId)
|
// F2:构建模拟导出树映射(段序列 → 主模型 PathId)
|
||||||
// 🔥 覆盖盒内对象的完整子孙树 + 祖先链(嵌套实例容器层):
|
// 🔥 纯 PathId 方案(无名字/链匹配):导出树 = 主模型树跳过 itemsToHide 的可见子编号
|
||||||
// 副实例碰撞对象可能是盒内几何对象的祖先(IsComposite 嵌套实例容器,
|
// (与 NW 的 ExportToNwd(ExcludeHiddenItems=true) 语义一致,已用固定导出文件对比验证)
|
||||||
// 如 Revit 门/窗实例——不在 ObjectsInBox 但可被碰撞),
|
// 副实例碰撞对象的 PathId(段序列)可直接查表 → 主模型 PathId
|
||||||
// 也可能是其嵌套子节点——三者都必须建映射
|
var hiddenSet = new HashSet<ModelItem>();
|
||||||
foreach (ModelItem boxItem in traversal.ObjectsInBox)
|
foreach (var h in traversal.ItemsToHide)
|
||||||
{
|
{
|
||||||
// 1) 子孙树(含自身)
|
hiddenSet.Add(h);
|
||||||
IEnumerable<ModelItem> nodesToMap;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
nodesToMap = boxItem.DescendantsAndSelf;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
nodesToMap = new[] { boxItem };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (ModelItem node in nodesToMap)
|
chainToMainPathId.Clear();
|
||||||
|
foreach (var model in document.Models)
|
||||||
{
|
{
|
||||||
if (node == null || string.IsNullOrWhiteSpace(node.DisplayName))
|
if (model.RootItem == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string chain = BuildAncestorChain(node);
|
BuildSegmentMap(model.RootItem, hiddenSet, "", document, chainToMainPathId);
|
||||||
if (string.IsNullOrWhiteSpace(chain))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
LogManager.Info($"[剖面盒批检测] 段序列映射: {chainToMainPathId.Count} 条");
|
||||||
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} 条");
|
|
||||||
|
|
||||||
string exportedPath = exporter.ExportToNwd(document, traversal, localModelPath);
|
string exportedPath = exporter.ExportToNwd(document, traversal, localModelPath);
|
||||||
return !string.IsNullOrEmpty(exportedPath) && File.Exists(exportedPath);
|
return !string.IsNullOrEmpty(exportedPath) && File.Exists(exportedPath);
|
||||||
@ -499,6 +453,9 @@ namespace NavisworksTransport.Core.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 去掉 ancestorChain 的第一个段(根模型文件名),剩余部分主/副实例一致(导出保留树结构)。
|
/// 去掉 ancestorChain 的第一个段(根模型文件名),剩余部分主/副实例一致(导出保留树结构)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <summary>
|
||||||
|
/// 去根:去掉层级链的文档根段(移动物体在副实例中按链解析用)。
|
||||||
|
/// </summary>
|
||||||
private static string TrimRootFromChain(string chain)
|
private static string TrimRootFromChain(string chain)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(chain))
|
if (string.IsNullOrWhiteSpace(chain))
|
||||||
@ -515,46 +472,6 @@ namespace NavisworksTransport.Core.Services
|
|||||||
return string.Join(" / ", segments.Skip(1).Select(s => s.Trim()));
|
return string.Join(" / ", segments.Skip(1).Select(s => s.Trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 链尾部匹配:从末层(对象名)逐级加深,找到唯一以该后缀结尾的主链映射。
|
|
||||||
/// 容忍主/副实例链的头部层数差异(文档根/模型根层)。
|
|
||||||
/// </summary>
|
|
||||||
private static MainItemPath TryMatchChainTail(string tailChain, Dictionary<string, MainItemPath> chainToMainPathId)
|
|
||||||
{
|
|
||||||
string[] segments = tailChain.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(s => s.Trim())
|
|
||||||
.Where(s => s.Length > 0)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (segments.Length == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从末 2 层开始,逐级加深到全链(最深匹配优先,避免重名误配)
|
|
||||||
int maxDepth = Math.Min(segments.Length, 6);
|
|
||||||
for (int depth = 2; depth <= maxDepth; depth++)
|
|
||||||
{
|
|
||||||
string suffix = string.Join(" / ", segments.Skip(segments.Length - depth));
|
|
||||||
var candidates = chainToMainPathId.Keys
|
|
||||||
.Where(k => k.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (candidates.Count == 1)
|
|
||||||
{
|
|
||||||
return chainToMainPathId[candidates[0]];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多候选 → 加深一层继续;0 候选 → 无匹配
|
|
||||||
if (candidates.Count == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造 ModelItem 的层级链(AncestorsAndSelf 的 DisplayName,'/' 分隔)。
|
/// 构造 ModelItem 的层级链(AncestorsAndSelf 的 DisplayName,'/' 分隔)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -586,6 +503,58 @@ namespace NavisworksTransport.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模拟导出树段序列编号:深度优先遍历主模型树,跳过 itemsToHide(NW ExcludeHiddenItems 语义),
|
||||||
|
/// 可见子按序编号——段序列与副实例(导出文件)的 PathId 段序列一致(已用固定导出文件对比验证)。
|
||||||
|
/// 构建 段序列 → 主模型 PathId 映射(纯 PathId,无名字/链匹配)。
|
||||||
|
/// </summary>
|
||||||
|
private static void BuildSegmentMap(
|
||||||
|
ModelItem node,
|
||||||
|
HashSet<ModelItem> hiddenSet,
|
||||||
|
string segmentPath,
|
||||||
|
Document document,
|
||||||
|
Dictionary<string, MainItemPath> chainToMainPathId)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前节点映射(文档根 segmentPath 为空——不被碰撞,跳过)
|
||||||
|
if (!string.IsNullOrEmpty(segmentPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = document.Models.CreatePathId(node);
|
||||||
|
chainToMainPathId[segmentPath] = new MainItemPath(path.ModelIndex, path.PathId);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int visibleIndex = 0;
|
||||||
|
foreach (var child in node.Children)
|
||||||
|
{
|
||||||
|
if (child == null || hiddenSet.Contains(child))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string childPath = string.IsNullOrEmpty(segmentPath)
|
||||||
|
? visibleIndex.ToString()
|
||||||
|
: segmentPath + "/" + visibleIndex;
|
||||||
|
BuildSegmentMap(child, hiddenSet, childPath, document, chainToMainPathId);
|
||||||
|
visibleIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<DetectionResult> RunDetectionAsync(
|
private static async Task<DetectionResult> RunDetectionAsync(
|
||||||
HttpClient client,
|
HttpClient client,
|
||||||
PathRoute route,
|
PathRoute route,
|
||||||
@ -707,56 +676,17 @@ namespace NavisworksTransport.Core.Services
|
|||||||
SecondaryPathId = (string)obj["pathId"]
|
SecondaryPathId = (string)obj["pathId"]
|
||||||
};
|
};
|
||||||
|
|
||||||
// F2:按层级链(去根)查盒内对象映射 → 主模型 PathId
|
// F2:纯 PathId 映射——副实例 PathId(段序列)直查模拟导出树表
|
||||||
string chain = (string)obj["ancestorChain"];
|
string secondaryPathId = mapping.SecondaryPathId;
|
||||||
if (!string.IsNullOrWhiteSpace(chain))
|
if (!string.IsNullOrWhiteSpace(secondaryPathId) && chainToMainPathId != null &&
|
||||||
{
|
chainToMainPathId.TryGetValue(secondaryPathId, out MainItemPath mainItem))
|
||||||
string tail = TrimRootFromChain(chain);
|
|
||||||
if (tail != null && chainToMainPathId != null && chainToMainPathId.TryGetValue(tail, out MainItemPath mainItem))
|
|
||||||
{
|
{
|
||||||
mapping.MainPathId = mainItem.PathId;
|
mapping.MainPathId = mainItem.PathId;
|
||||||
mapping.MainModelIndex = mainItem.ModelIndex;
|
mapping.MainModelIndex = mainItem.ModelIndex;
|
||||||
}
|
}
|
||||||
else if (tail != null && chainToMainPathId != null)
|
|
||||||
{
|
|
||||||
// 🔥 精确匹配失败 → 尾部匹配(容忍根/模型根层数差异):
|
|
||||||
// 主链根=文档名(Factory.nwd)+模型根,副链根=导出模型根,去根后差一层
|
|
||||||
MainItemPath tailMatched = TryMatchChainTail(tail, chainToMainPathId);
|
|
||||||
if (tailMatched != null)
|
|
||||||
{
|
|
||||||
mapping.MainPathId = tailMatched.PathId;
|
|
||||||
mapping.MainModelIndex = tailMatched.ModelIndex;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 🔥 映射诊断:打印碰撞对象链与盒内映射候选,定位匹配失败原因
|
LogManager.Warning($"[F2映射] {name}: 副实例PathId未匹配主模型段序列表 (pathId={secondaryPathId})");
|
||||||
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}]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.CollidedObjects.Add(name);
|
result.CollidedObjects.Add(name);
|
||||||
|
|||||||
@ -600,6 +600,85 @@ namespace NavisworksTransport.Core.Services
|
|||||||
return new { error = "无活动文档" };
|
return new { error = "无活动文档" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(GetOptionalQueryValue(query, "mode"), "simulate", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// 模拟导出树:按盒重算盒内对象 + itemsToHide,遍历主树跳过隐藏节点,
|
||||||
|
// 输出模拟导出树的段序列 + 名字(与副实例实际导出树对比验证)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
double x1 = ParseRequiredDouble(query, "x1");
|
||||||
|
double y1 = ParseRequiredDouble(query, "y1");
|
||||||
|
double z1 = ParseRequiredDouble(query, "z1");
|
||||||
|
double x2 = ParseRequiredDouble(query, "x2");
|
||||||
|
double y2 = ParseRequiredDouble(query, "y2");
|
||||||
|
double z2 = ParseRequiredDouble(query, "z2");
|
||||||
|
|
||||||
|
var bounds = new BoundingBox3D(
|
||||||
|
new Point3D(Math.Min(x1, x2), Math.Min(y1, y2), Math.Min(z1, z2)),
|
||||||
|
new Point3D(Math.Max(x1, x2), Math.Max(y1, y2), Math.Max(z1, z2)));
|
||||||
|
|
||||||
|
var exporter = new NavisworksTransport.Core.SectionBoxExporter();
|
||||||
|
var traversal = exporter.GetObjectsAndHiddenItems(doc, bounds);
|
||||||
|
|
||||||
|
var hiddenSet = new HashSet<ModelItem>();
|
||||||
|
foreach (var h in traversal.ItemsToHide)
|
||||||
|
{
|
||||||
|
hiddenSet.Add(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tree = new List<string>();
|
||||||
|
int maxNodes = int.TryParse(GetOptionalQueryValue(query, "maxNodes"), out int m) && m > 0 ? m : 300;
|
||||||
|
|
||||||
|
foreach (var model in doc.Models)
|
||||||
|
{
|
||||||
|
if (model.RootItem == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟导出:跳过隐藏节点(含子树),可见子按序编号
|
||||||
|
// PathId 格式:从文档根的子开始(文档根/模型根无段)
|
||||||
|
SimulateExportTree(model.RootItem, hiddenSet, "", tree, maxNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
documentName = doc.FileName,
|
||||||
|
nodeCount = tree.Count,
|
||||||
|
objectsInBox = traversal.ObjectsInBox.Count,
|
||||||
|
itemsToHide = traversal.ItemsToHide.Count,
|
||||||
|
tree
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new { error = ex.Message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(GetOptionalQueryValue(query, "mode"), "full", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// 完整树输出(名字 + PathId)——导出模型 vs 主模型树关系研究
|
||||||
|
var tree = new List<string>();
|
||||||
|
int maxNodes = int.TryParse(GetOptionalQueryValue(query, "maxNodes"), out int m) && m > 0 ? m : 500;
|
||||||
|
foreach (var model in doc.Models)
|
||||||
|
{
|
||||||
|
if (model.RootItem == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DumpTree(model.RootItem, 0, tree, maxNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
documentName = doc.FileName,
|
||||||
|
nodeCount = tree.Count,
|
||||||
|
tree
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
string matchName = GetOptionalQueryValue(query, "name");
|
string matchName = GetOptionalQueryValue(query, "name");
|
||||||
if (!string.IsNullOrWhiteSpace(matchName))
|
if (!string.IsNullOrWhiteSpace(matchName))
|
||||||
{
|
{
|
||||||
@ -626,13 +705,62 @@ namespace NavisworksTransport.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nameObjects = new List<object>();
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var b = item.BoundingBox();
|
||||||
|
string pathId = "";
|
||||||
|
int modelIndex = -1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = doc.Models.CreatePathId(item);
|
||||||
|
pathId = path.PathId;
|
||||||
|
modelIndex = path.ModelIndex;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
nameObjects.Add(new
|
||||||
|
{
|
||||||
|
name = item.DisplayName,
|
||||||
|
pathId = $"{modelIndex}/{pathId}",
|
||||||
|
bbox = $"({b.Min.X:F2},{b.Min.Y:F2},{b.Min.Z:F2})~({b.Max.X:F2},{b.Max.Y:F2},{b.Max.Z:F2})",
|
||||||
|
size = $"{b.Max.X - b.Min.X:F2} x {b.Max.Y - b.Min.Y:F2} x {b.Max.Z - b.Min.Z:F2}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception bEx)
|
||||||
|
{
|
||||||
|
nameObjects.Add(new { name = item.DisplayName, bbox = $"ERROR: {bEx.Message}" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameObjects.Count >= 3)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
documentName = doc.FileName,
|
documentName = doc.FileName,
|
||||||
modelCount = doc.Models.Count,
|
modelCount = doc.Models.Count,
|
||||||
nameMatch = matchName,
|
nameMatch = matchName,
|
||||||
matchCount,
|
matchCount,
|
||||||
chains
|
chains,
|
||||||
|
objects = nameObjects
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -669,6 +797,82 @@ namespace NavisworksTransport.Core.Services
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SimulateExportTree(ModelItem node, HashSet<ModelItem> hiddenSet, string segmentPath, List<string> lines, int maxNodes)
|
||||||
|
{
|
||||||
|
if (node == null || lines.Count >= maxNodes)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NW ExcludeHiddenItems 语义:隐藏节点(含子树)不导出;可见子按序编号
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lines.Add($"{node.DisplayName ?? ""} [{segmentPath}]");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int visibleIndex = 0;
|
||||||
|
foreach (var child in node.Children)
|
||||||
|
{
|
||||||
|
if (child == null || hiddenSet.Contains(child))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string childPath = string.IsNullOrEmpty(segmentPath)
|
||||||
|
? visibleIndex.ToString()
|
||||||
|
: segmentPath + "/" + visibleIndex;
|
||||||
|
SimulateExportTree(child, hiddenSet, childPath, lines, maxNodes);
|
||||||
|
visibleIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DumpTree(ModelItem node, int depth, List<string> lines, int maxNodes)
|
||||||
|
{
|
||||||
|
if (node == null || lines.Count >= maxNodes)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string pathId = "";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(node);
|
||||||
|
pathId = path.PathId;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.Add($"{new string(' ', depth * 2)}{node.DisplayName ?? ""} [{pathId}]");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
lines.Add($"{new string(' ', depth * 2)}<error>");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var child in node.Children)
|
||||||
|
{
|
||||||
|
DumpTree(child, depth + 1, lines, maxNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private object BuildTraversableItemsPayload()
|
private object BuildTraversableItemsPayload()
|
||||||
{
|
{
|
||||||
// 【实验端点】查询当前文档的可通行物流属性对象(验证通道属性是否在模型文件中)
|
// 【实验端点】查询当前文档的可通行物流属性对象(验证通道属性是否在模型文件中)
|
||||||
|
|||||||
@ -556,6 +556,15 @@ namespace NavisworksTransport.Utils
|
|||||||
throw new InvalidOperationException("没有活动的文档");
|
throw new InvalidOperationException("没有活动的文档");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔥 非法值防御:相机/目标坐标非有限值(NaN/Infinity)会导致
|
||||||
|
// AlignDirection(NaN) 触发 NW 原生崩溃(非托管,无法 try-catch)——
|
||||||
|
// 异常包围盒(巨大/无效实例)是常见来源,防御后跳过本次视角调整。
|
||||||
|
if (!IsFinite(cameraPosition) || !IsFinite(targetPoint))
|
||||||
|
{
|
||||||
|
LogManager.Warning($"[ViewpointHelper] 视角参数非有限值,跳过视角调整: camera=({cameraPosition.X},{cameraPosition.Y},{cameraPosition.Z}), target=({targetPoint.X},{targetPoint.Y},{targetPoint.Z})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();
|
Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();
|
||||||
newViewpoint.Position = cameraPosition;
|
newViewpoint.Position = cameraPosition;
|
||||||
|
|
||||||
@ -567,6 +576,15 @@ namespace NavisworksTransport.Utils
|
|||||||
targetPoint.Y - cameraPosition.Y,
|
targetPoint.Y - cameraPosition.Y,
|
||||||
targetPoint.Z - cameraPosition.Z
|
targetPoint.Z - cameraPosition.Z
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🔥 零方向防御:相机与目标重合(距离近零)→ Normalize 产生 NaN → NW 崩溃
|
||||||
|
double lookLengthSq = lookDirection.X * lookDirection.X + lookDirection.Y * lookDirection.Y + lookDirection.Z * lookDirection.Z;
|
||||||
|
if (lookLengthSq < 1e-12)
|
||||||
|
{
|
||||||
|
LogManager.Warning($"[ViewpointHelper] 相机与目标距离近零,跳过视角调整: camera=({cameraPosition.X},{cameraPosition.Y},{cameraPosition.Z})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lookDirection.Normalize();
|
lookDirection.Normalize();
|
||||||
newViewpoint.AlignDirection(lookDirection);
|
newViewpoint.AlignDirection(lookDirection);
|
||||||
newViewpoint.AlignUp(upVector);
|
newViewpoint.AlignUp(upVector);
|
||||||
@ -581,6 +599,16 @@ namespace NavisworksTransport.Utils
|
|||||||
doc.CurrentViewpoint.CopyFrom(newViewpoint);
|
doc.CurrentViewpoint.CopyFrom(newViewpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsFinite(Point3D p)
|
||||||
|
{
|
||||||
|
return IsFinite(p.X) && IsFinite(p.Y) && IsFinite(p.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFinite(double value)
|
||||||
|
{
|
||||||
|
return !double.IsNaN(value) && !double.IsInfinity(value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 应用视角并缩放视野(使用 ZoomBox)
|
/// 应用视角并缩放视野(使用 ZoomBox)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user