refactor: ModelItemTreeWalker 通用遍历工具(统一 IsHidden 剪枝 + 聚合盒剪枝 + 后序传播)
新增 src/Utils/ModelItemTreeWalker.cs: - WalkVisible/WalkModels:深度遍历,IsHidden 剪枝内置(父隐藏 → 整棵子树跳过) - 回调返回 ModelItemWalkAction(Prune/Continue/Hit)——剪枝条件与深入策略由调用方决定 - Hit 触发父级后序传播(postHitVisit)——共享祖先只上传一次(消除 K×D 枚举) - onHidden 回调(隐藏节点统计) 迁移: - SectionBoxExporter.TraverseAndCollect → walker(聚合盒剪枝 + 后序传播语义不变) - ModelItemAnalysisHelper.GetAllVisibleGeometryItems → walker(栈式循环替换,统计保留) 修复: - run-unit-tests.bat 过滤器排除 NavisworksIntegrationF1(F1 测试此前会被单元测试集选中, 无主 NW 环境时失败) 验证:单元 244/244、集成 25/25(SectionBox 61ms)、Architecture 111ms(无回退)、对象 228 一致
This commit is contained in:
parent
da81672782
commit
818a5e0ad7
@ -339,6 +339,7 @@
|
|||||||
<Compile Include="src\Utils\CoordinateConverter.cs" />
|
<Compile Include="src\Utils\CoordinateConverter.cs" />
|
||||||
<Compile Include="src\Utils\FloorDetector.cs" />
|
<Compile Include="src\Utils\FloorDetector.cs" />
|
||||||
<Compile Include="src\Utils\ModelItemAnalysisHelper.cs" />
|
<Compile Include="src\Utils\ModelItemAnalysisHelper.cs" />
|
||||||
|
<Compile Include="src\Utils\ModelItemTreeWalker.cs" />
|
||||||
<Compile Include="src\Utils\GeometryHelper.cs" />
|
<Compile Include="src\Utils\GeometryHelper.cs" />
|
||||||
<Compile Include="src\Utils\GeometryCacheManager.cs" />
|
<Compile Include="src\Utils\GeometryCacheManager.cs" />
|
||||||
<Compile Include="src\Utils\DialogHelper.cs" />
|
<Compile Include="src\Utils\DialogHelper.cs" />
|
||||||
|
|||||||
@ -48,7 +48,7 @@ if errorlevel 1 (
|
|||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
%VSTEST_PATH% "bin\x64\Release\NavisworksTransport.UnitTests.dll" /Platform:x64 /TestAdapterPath:%TEST_ADAPTER_PATH% /TestCaseFilter:"TestCategory!=NavisworksIntegration"
|
%VSTEST_PATH% "bin\x64\Release\NavisworksTransport.UnitTests.dll" /Platform:x64 /TestAdapterPath:%TEST_ADAPTER_PATH% /TestCaseFilter:"TestCategory!=NavisworksIntegration&TestCategory!=NavisworksIntegrationF1"
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo Unit tests failed!
|
echo Unit tests failed!
|
||||||
exit /b 1
|
exit /b 1
|
||||||
|
|||||||
@ -279,12 +279,24 @@ namespace NavisworksTransport.Core
|
|||||||
var visibleSet = new HashSet<ModelItem>(); // 收集需要保留的节点(对象+祖先)
|
var visibleSet = new HashSet<ModelItem>(); // 收集需要保留的节点(对象+祖先)
|
||||||
|
|
||||||
// 第一遍:遍历整个模型树,收集盒内对象和可见节点
|
// 第一遍:遍历整个模型树,收集盒内对象和可见节点
|
||||||
|
// (统一走 ModelItemTreeWalker:IsHidden 剪枝 + 组节点聚合盒剪枝 + 后序传播内置)
|
||||||
foreach (var model in document.Models)
|
foreach (var model in document.Models)
|
||||||
{
|
{
|
||||||
var rootItem = model.RootItem;
|
if (model.RootItem == null)
|
||||||
if (rootItem != null)
|
|
||||||
{
|
{
|
||||||
TraverseAndCollect(model.RootItem, sectionBox, result.ObjectsInBox, visibleSet, prune);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rootHit = ModelItemTreeWalker.WalkVisible(
|
||||||
|
model.RootItem,
|
||||||
|
node => TraverseVisit(node, sectionBox, result.ObjectsInBox, visibleSet, prune),
|
||||||
|
postHitVisit: node => visibleSet.Add(node),
|
||||||
|
onHidden: null);
|
||||||
|
|
||||||
|
// 根节点本身也是命中对象的祖先(原 AncestorsAndSelf 含根)
|
||||||
|
if (rootHit)
|
||||||
|
{
|
||||||
|
visibleSet.Add(model.RootItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,73 +317,64 @@ namespace NavisworksTransport.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 递归遍历收集盒内对象和可见节点。
|
/// 模型树遍历访问回调(ModelItemTreeWalker 调用):
|
||||||
/// 返回:本子树是否存在盒内对象(后序传播用——命中路径的祖先由父级逐层加入 visibleSet)。
|
/// - 组节点:聚合盒剪枝(prune 时)→ Prune 或 Continue
|
||||||
|
/// - 几何节点:盒相交 + 有体积 → Hit(加入盒内对象与可见集合),否则 Prune(几何节点不深入)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool TraverseAndCollect(ModelItem item, BoundingBox3D sectionBox,
|
private static ModelItemWalkAction TraverseVisit(
|
||||||
List<ModelItem> objectsInBox, HashSet<ModelItem> visibleSet, bool prune)
|
ModelItem item,
|
||||||
|
BoundingBox3D sectionBox,
|
||||||
|
List<ModelItem> objectsInBox,
|
||||||
|
HashSet<ModelItem> visibleSet,
|
||||||
|
bool prune)
|
||||||
{
|
{
|
||||||
if (item == null || item.IsHidden) return false;
|
|
||||||
|
|
||||||
bool hasGeometry = item.HasGeometry;
|
bool hasGeometry = item.HasGeometry;
|
||||||
|
|
||||||
// 子树剪枝:非几何节点(组节点)先查聚合包围盒(NW 内部缓存,O(1))。
|
// 子树剪枝:非几何节点(组节点)先查聚合包围盒(NW 内部缓存,O(1))。
|
||||||
// 组节点盒与剖面盒不相交 → 其整棵子树内不可能存在盒内对象 → 跳过全部后代。
|
// 组节点盒与剖面盒不相交 → 其整棵子树内不可能存在盒内对象 → 跳过全部后代。
|
||||||
// 收益:大型模型下远处楼层/区域一次调用剪掉整棵子树,省下所有后代的
|
// 收益:大型模型下远处楼层/区域一次调用剪掉整棵子树,省下所有后代的
|
||||||
// Children/IsHidden/HasGeometry API 调用。
|
// Children/IsHidden/HasGeometry API 调用。
|
||||||
if (prune && !hasGeometry)
|
if (!hasGeometry)
|
||||||
{
|
{
|
||||||
var groupBox = item.BoundingBox();
|
if (prune && !BoundingBoxesIntersect(item.BoundingBox(), sectionBox))
|
||||||
if (!BoundingBoxesIntersect(groupBox, sectionBox))
|
|
||||||
{
|
{
|
||||||
return false;
|
return ModelItemWalkAction.Prune;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有几何体,检查是否在盒内
|
|
||||||
if (hasGeometry)
|
|
||||||
{
|
|
||||||
var bbox = item.BoundingBox();
|
|
||||||
|
|
||||||
// 检查是否有体积(三个方向尺寸都大于0)
|
|
||||||
double sizeX = bbox.Max.X - bbox.Min.X;
|
|
||||||
double sizeY = bbox.Max.Y - bbox.Min.Y;
|
|
||||||
double sizeZ = bbox.Max.Z - bbox.Min.Z;
|
|
||||||
bool hasVolume = sizeX > 0.0001 && sizeY > 0.0001 && sizeZ > 0.0001;
|
|
||||||
|
|
||||||
// 有体积且与剖面盒相交 → 是目标对象(只加入自己,祖先由父级后序上传)
|
|
||||||
if (hasVolume && BoundingBoxesIntersect(bbox, sectionBox))
|
|
||||||
{
|
|
||||||
objectsInBox.Add(item);
|
|
||||||
visibleSet.Add(item);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 找到几何体,停止向下遍历(原逻辑)
|
return ModelItemWalkAction.Continue;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有几何体,继续遍历子节点;后序传播:任一子命中 → 自己是祖先,加入 visibleSet
|
// 几何体节点:检查是否在盒内
|
||||||
bool anyChildHit = false;
|
var bbox = item.BoundingBox();
|
||||||
foreach (var child in item.Children)
|
|
||||||
{
|
|
||||||
if (TraverseAndCollect(child, sectionBox, objectsInBox, visibleSet, prune))
|
|
||||||
{
|
|
||||||
anyChildHit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anyChildHit)
|
// 检查是否有体积(三个方向尺寸都大于0)
|
||||||
|
double sizeX = bbox.Max.X - bbox.Min.X;
|
||||||
|
double sizeY = bbox.Max.Y - bbox.Min.Y;
|
||||||
|
double sizeZ = bbox.Max.Z - bbox.Min.Z;
|
||||||
|
bool hasVolume = sizeX > 0.0001 && sizeY > 0.0001 && sizeZ > 0.0001;
|
||||||
|
|
||||||
|
// 有体积且与剖面盒相交 → 是目标对象(只加入自己,祖先由后序传播上传)
|
||||||
|
if (hasVolume && BoundingBoxesIntersect(bbox, sectionBox))
|
||||||
{
|
{
|
||||||
|
objectsInBox.Add(item);
|
||||||
visibleSet.Add(item);
|
visibleSet.Add(item);
|
||||||
|
return ModelItemWalkAction.Hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
return anyChildHit;
|
// 找到几何体但不在盒内,停止向下遍历(原逻辑)
|
||||||
|
return ModelItemWalkAction.Prune;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 【使用原来的兄弟节点算法】计算需要隐藏的节点
|
/// 获取剖面盒内的所有对象(公开方法供外部调用)
|
||||||
|
/// 【已优化】内部使用 GetObjectsAndHiddenItems 实现一次遍历
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
public List<ModelItem> GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox)
|
||||||
|
{
|
||||||
|
var result = GetObjectsAndHiddenItems(document, sectionBox);
|
||||||
|
return result.ObjectsInBox;
|
||||||
|
}
|
||||||
|
|
||||||
private void CalculateHiddenItems(HashSet<ModelItem> visibleSet, ModelItemCollection itemsToHide)
|
private void CalculateHiddenItems(HashSet<ModelItem> visibleSet, ModelItemCollection itemsToHide)
|
||||||
{
|
{
|
||||||
// 1. 收集唯一的父节点
|
// 1. 收集唯一的父节点
|
||||||
@ -404,20 +407,7 @@ namespace NavisworksTransport.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static bool BoundingBoxesIntersect(BoundingBox3D a, BoundingBox3D b)
|
||||||
/// 获取剖面盒内的所有对象(公开方法供外部调用)
|
|
||||||
/// 【已优化】内部使用 GetObjectsAndHiddenItems 实现一次遍历
|
|
||||||
/// </summary>
|
|
||||||
public List<ModelItem> GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox)
|
|
||||||
{
|
|
||||||
var result = GetObjectsAndHiddenItems(document, sectionBox);
|
|
||||||
return result.ObjectsInBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查两个包围盒是否相交
|
|
||||||
/// </summary>
|
|
||||||
private bool BoundingBoxesIntersect(BoundingBox3D a, BoundingBox3D b)
|
|
||||||
{
|
{
|
||||||
return (a.Min.X <= b.Max.X && a.Max.X >= b.Min.X) &&
|
return (a.Min.X <= b.Max.X && a.Max.X >= b.Min.X) &&
|
||||||
(a.Min.Y <= b.Max.Y && a.Max.Y >= b.Min.Y) &&
|
(a.Min.Y <= b.Max.Y && a.Max.Y >= b.Min.Y) &&
|
||||||
|
|||||||
@ -382,17 +382,8 @@ namespace NavisworksTransport.Utils
|
|||||||
var totalModelItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf.Count();
|
var totalModelItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf.Count();
|
||||||
LogManager.Info($"[几何体获取] 模型总数统计: {totalModelItems} 个模型项");
|
LogManager.Info($"[几何体获取] 模型总数统计: {totalModelItems} 个模型项");
|
||||||
|
|
||||||
// 使用 Stack 模拟递归避免栈溢出
|
|
||||||
// Stack 初始容量设为 1000 以减少扩容
|
|
||||||
var stack = new Stack<ModelItem>(1000);
|
|
||||||
var visibleItems = new List<ModelItem>();
|
var visibleItems = new List<ModelItem>();
|
||||||
|
|
||||||
// 将所有模型的根节点压入栈
|
|
||||||
foreach (var model in Application.ActiveDocument.Models)
|
|
||||||
{
|
|
||||||
stack.Push(model.RootItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统计信息
|
// 统计信息
|
||||||
int hiddenSkipped = 0; // 跳过的隐藏节点(包括其子节点)
|
int hiddenSkipped = 0; // 跳过的隐藏节点(包括其子节点)
|
||||||
int visibleCount = 0; // 可见节点总数
|
int visibleCount = 0; // 可见节点总数
|
||||||
@ -404,28 +395,24 @@ namespace NavisworksTransport.Utils
|
|||||||
int mixedCount = 0; // 混合类型几何体
|
int mixedCount = 0; // 混合类型几何体
|
||||||
int noGeometryCount = 0; // 无几何体节点
|
int noGeometryCount = 0; // 无几何体节点
|
||||||
|
|
||||||
while (stack.Count > 0)
|
// 统一走 ModelItemTreeWalker:IsHidden 剪枝(父隐藏 → 整棵子树跳过)内置
|
||||||
{
|
ModelItemTreeWalker.WalkModels(
|
||||||
var item = stack.Pop();
|
Application.ActiveDocument.Models,
|
||||||
|
item =>
|
||||||
// 关键剪枝:如果节点显式隐藏,则跳过其整个分支(它的所有子节点都不可见)
|
|
||||||
if (item.IsHidden)
|
|
||||||
{
|
{
|
||||||
hiddenSkipped++;
|
visibleCount++;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
visibleCount++;
|
// 🔥 优化:只提取复合对象(IsComposite = true)或独立实体节点(HasGeometry)
|
||||||
|
// 原因:复合对象(如窗户)包含多个实体节点(玻璃、框架、把手)
|
||||||
|
// 我们只需要检测复合对象,不需要检测其子节点
|
||||||
|
bool isComposite = item.IsComposite;
|
||||||
|
bool hasGeometry = item.HasGeometry;
|
||||||
|
|
||||||
// 🔥 优化:只提取复合对象(IsComposite = true)或独立实体节点(HasGeometry)
|
if (!isComposite && !hasGeometry)
|
||||||
// 原因:复合对象(如窗户)包含多个实体节点(玻璃、框架、把手)
|
{
|
||||||
// 我们只需要检测复合对象,不需要检测其子节点
|
// 空节点:继续遍历子节点
|
||||||
bool isComposite = item.IsComposite;
|
return ModelItemWalkAction.Continue;
|
||||||
bool hasGeometry = item.HasGeometry;
|
}
|
||||||
|
|
||||||
if (isComposite || hasGeometry)
|
|
||||||
{
|
|
||||||
bool addedCurrentItem = false;
|
|
||||||
|
|
||||||
// 复合对象或独立实体节点:检查几何体类型
|
// 复合对象或独立实体节点:检查几何体类型
|
||||||
var geometry = item.Geometry;
|
var geometry = item.Geometry;
|
||||||
@ -442,9 +429,11 @@ namespace NavisworksTransport.Utils
|
|||||||
else
|
else
|
||||||
mixedCount++;
|
mixedCount++;
|
||||||
visibleItems.Add(item); // 只保留包含三角形的几何体
|
visibleItems.Add(item); // 只保留包含三角形的几何体
|
||||||
addedCurrentItem = true;
|
// 已添加 → 截断子树(本节点直接作为几何候选)
|
||||||
|
return ModelItemWalkAction.Prune;
|
||||||
}
|
}
|
||||||
else if (primitiveTypes == PrimitiveTypes.Lines)
|
|
||||||
|
if (primitiveTypes == PrimitiveTypes.Lines)
|
||||||
{
|
{
|
||||||
lineOnlyCount++;
|
lineOnlyCount++;
|
||||||
// 不添加:纯线条几何体过滤掉
|
// 不添加:纯线条几何体过滤掉
|
||||||
@ -468,7 +457,7 @@ namespace NavisworksTransport.Utils
|
|||||||
{
|
{
|
||||||
mixedCount++;
|
mixedCount++;
|
||||||
visibleItems.Add(item); // 其他混合类型保留
|
visibleItems.Add(item); // 其他混合类型保留
|
||||||
addedCurrentItem = true;
|
return ModelItemWalkAction.Prune;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -477,25 +466,10 @@ namespace NavisworksTransport.Utils
|
|||||||
// 不添加:无几何体数据
|
// 不添加:无几何体数据
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只有当前节点已经可直接作为几何候选时,才截断子树。
|
// 当前节点没有可用三角几何:继续向下遍历,避免把真实几何子节点整棵截断
|
||||||
// 如果当前节点本身没有可用三角几何,则继续向下遍历,避免把真实几何子节点整棵截断。
|
return ModelItemWalkAction.Continue;
|
||||||
if (!addedCurrentItem)
|
},
|
||||||
{
|
onHidden: item => hiddenSkipped++);
|
||||||
foreach (var child in item.Children)
|
|
||||||
{
|
|
||||||
stack.Push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 空节点:继续遍历子节点
|
|
||||||
foreach (var child in item.Children)
|
|
||||||
{
|
|
||||||
stack.Push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var elapsed = (DateTime.Now - startTime).TotalMilliseconds;
|
var elapsed = (DateTime.Now - startTime).TotalMilliseconds;
|
||||||
|
|
||||||
|
|||||||
120
src/Utils/ModelItemTreeWalker.cs
Normal file
120
src/Utils/ModelItemTreeWalker.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Autodesk.Navisworks.Api;
|
||||||
|
|
||||||
|
namespace NavisworksTransport.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 模型树遍历访问结果(回调决定本节点的深入/剪枝/命中行为)。
|
||||||
|
/// </summary>
|
||||||
|
public enum ModelItemWalkAction
|
||||||
|
{
|
||||||
|
/// <summary>剪掉整棵子树(不再访问任何后代)</summary>
|
||||||
|
Prune,
|
||||||
|
|
||||||
|
/// <summary>继续访问后代节点</summary>
|
||||||
|
Continue,
|
||||||
|
|
||||||
|
/// <summary>命中:停止深入本节点子树,并触发父级后序传播(postHitVisit)</summary>
|
||||||
|
Hit
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型树深度遍历通用工具。
|
||||||
|
///
|
||||||
|
/// 内置语义:
|
||||||
|
/// - IsHidden 剪枝:节点隐藏 → 整棵子树跳过(Navisworks 层级可见性规则:
|
||||||
|
/// 父隐藏则所有子节点都不可见,无需深入)
|
||||||
|
/// - 回调返回 Hit 时触发父级后序传播(postHitVisit)——用于收集
|
||||||
|
/// "命中对象的所有祖先"(共享祖先只被上传一次,避免 K×D 次枚举)
|
||||||
|
///
|
||||||
|
/// 用途:剖面盒导出(SectionBoxExporter)、可见几何体收集
|
||||||
|
/// (ModelItemAnalysisHelper)等模型树遍历统一复用。
|
||||||
|
/// </summary>
|
||||||
|
public static class ModelItemTreeWalker
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 深度遍历模型树。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">当前节点(通常从 model.RootItem 开始)</param>
|
||||||
|
/// <param name="visit">访问回调(决定 Prune/Continue/Hit)</param>
|
||||||
|
/// <param name="postHitVisit">后序传播:子树有 Hit 时对祖先调用(用于收集祖先集合)</param>
|
||||||
|
/// <param name="onHidden">隐藏节点通知(仅统计用,隐藏节点不会触发 visit)</param>
|
||||||
|
/// <returns>本子树是否存在 Hit</returns>
|
||||||
|
public static bool WalkVisible(
|
||||||
|
ModelItem item,
|
||||||
|
Func<ModelItem, ModelItemWalkAction> visit,
|
||||||
|
Action<ModelItem> postHitVisit = null,
|
||||||
|
Action<ModelItem> onHidden = null)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键剪枝:节点隐藏 → 整棵子树跳过(其所有子节点都不可见)
|
||||||
|
if (item.IsHidden)
|
||||||
|
{
|
||||||
|
onHidden?.Invoke(item);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelItemWalkAction action = visit(item);
|
||||||
|
if (action == ModelItemWalkAction.Prune)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == ModelItemWalkAction.Hit)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue:深入子节点;任一子节点命中 → 本节点触发后序传播
|
||||||
|
bool anyChildHit = false;
|
||||||
|
foreach (var child in item.Children)
|
||||||
|
{
|
||||||
|
if (WalkVisible(child, visit, postHitVisit, onHidden))
|
||||||
|
{
|
||||||
|
anyChildHit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyChildHit)
|
||||||
|
{
|
||||||
|
postHitVisit?.Invoke(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyChildHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 遍历多个模型的根(Application.ActiveDocument.Models 场景)。
|
||||||
|
/// 返回任意模型根命中时根节点本身也触发 postHitVisit(根是命中对象祖先之一)。
|
||||||
|
/// </summary>
|
||||||
|
public static bool WalkModels(
|
||||||
|
IEnumerable<Model> models,
|
||||||
|
Func<ModelItem, ModelItemWalkAction> visit,
|
||||||
|
Action<ModelItem> postHitVisit = null,
|
||||||
|
Action<ModelItem> onHidden = null)
|
||||||
|
{
|
||||||
|
bool anyHit = false;
|
||||||
|
foreach (var model in models)
|
||||||
|
{
|
||||||
|
if (model?.RootItem == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rootHit = WalkVisible(model.RootItem, visit, postHitVisit, onHidden);
|
||||||
|
if (rootHit)
|
||||||
|
{
|
||||||
|
postHitVisit?.Invoke(model.RootItem);
|
||||||
|
anyHit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyHit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user