diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs
index 9a35c0e..485740f 100644
--- a/src/Core/Animation/PathAnimationManager.cs
+++ b/src/Core/Animation/PathAnimationManager.cs
@@ -1963,7 +1963,8 @@ namespace NavisworksTransport.Core.Animation
///
- /// 安全获取ModelItem的DisplayName,避免访问已释放对象的警告
+ /// 安全获取ModelItem的显示名称,避免访问已释放对象的警告
+ /// 优先使用ClassName,其次DisplayName
///
/// ModelItem对象
/// 安全的显示名称
@@ -1976,8 +1977,9 @@ namespace NavisworksTransport.Core.Animation
return "NULL";
}
- // 尝试访问DisplayName,如果对象已被释放会抛出异常
- return item.DisplayName ?? "未命名对象";
+ // 优先使用ClassName,其次DisplayName
+ // 某些几何体DisplayName为空但ClassName有值(如"Pipe", "Column"等)
+ return item.ClassName ?? item.DisplayName ?? "未命名对象";
}
catch (System.ObjectDisposedException)
{
@@ -3952,17 +3954,31 @@ namespace NavisworksTransport.Core.Animation
continue; // 跳过该节点及其子节点(剪枝)
}
- // 相交:如果是复合对象或有几何数据,加入列表
+ // 相交:处理对象
+ // 🔥 策略:复合对象作为整体添加(有名字),不遍历子节点
+ // 只有独立几何对象(无复合父级)才添加
if (item.IsComposite)
{
- objectsInClipBox.Add(item);
- intersectCount++;
- // 复合对象不遍历子节点
+ // 复合对象:用棱上采样精筛,排除地基等(自适应采样)
+ bool edgeIntersects = SectionClipHelper.IntersectsByEdgeSampling(itemBox, clipBox, minSamplesPerEdge: 3);
+
+ if (edgeIntersects)
+ {
+ objectsInClipBox.Add(item);
+ intersectCount++;
+ }
+ // 复合对象不遍历子节点(已作为整体添加)
}
else if (item.HasGeometry)
{
- objectsInClipBox.Add(item);
- intersectCount++;
+ // 独立几何对象(叶子节点):精筛后添加(自适应采样)
+ bool edgeIntersects = SectionClipHelper.IntersectsByEdgeSampling(itemBox, clipBox, minSamplesPerEdge: 3);
+
+ if (edgeIntersects)
+ {
+ objectsInClipBox.Add(item);
+ intersectCount++;
+ }
}
else
{
diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
index 7d23aad..cb5a8bf 100644
--- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
+++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
@@ -2667,7 +2667,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
foreach (var stat in objectStats)
{
- var objectName = stat.Object.DisplayName ?? "未知对象";
+ // 获取对象名称(如果当前节点无名,向上查找有名字的父节点)
+ var objectName = GetEffectiveObjectName(stat.Object);
var reason = GetHotspotReason(objectName, stat.Object);
hotspots.Add(new CollisionHotspotInfo
diff --git a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
index 94adebf..abee541 100644
--- a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
+++ b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Threading.Tasks;
@@ -1907,8 +1908,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
sb.AppendLine("✅ 剖面盒设置成功(10m x 10m x 5m)");
sb.AppendLine();
- // 3. 统计对象数量
- sb.AppendLine("【3. 对象数量统计】");
+ // 3. 统计对象数量(包围盒相交)
+ sb.AppendLine("【3. 对象数量统计(包围盒相交)】");
int totalCount, insideCount, outsideCount;
SectionClipHelper.CountObjectsInClipBox(out totalCount, out insideCount, out outsideCount);
@@ -1921,6 +1922,46 @@ namespace NavisworksTransport.UI.WPF.ViewModels
sb.AppendLine($"过滤率: {filterRate:F1}%");
}
sb.AppendLine();
+
+ // 3b. 统计对象数量(角点检测)
+ sb.AppendLine("【3b. 对象数量统计(角点检测)】");
+ int totalCount2, insideCount2, outsideCount2, largeBoxFiltered;
+ SectionClipHelper.CountObjectsInClipBoxByCorners(out totalCount2, out insideCount2, out outsideCount2, out largeBoxFiltered);
+
+ sb.AppendLine($"总对象数: {totalCount2}");
+ sb.AppendLine($"剖面盒内(角点): {insideCount2}");
+ sb.AppendLine($"剖面盒外: {outsideCount2}");
+ sb.AppendLine($"大包围盒过滤: {largeBoxFiltered}");
+ if (totalCount2 > 0)
+ {
+ double filterRate2 = (double)outsideCount2 / totalCount2 * 100;
+ sb.AppendLine($"过滤率: {filterRate2:F1}%");
+ }
+ if (largeBoxFiltered > 0)
+ {
+ sb.AppendLine($"⚠️ 发现 {largeBoxFiltered} 个大包围盒对象被过滤(如地基等)");
+ }
+ sb.AppendLine();
+
+ // 3c. 统计对象数量(棱上采样)
+ sb.AppendLine("【3c. 对象数量统计(棱上采样 - 用于检测穿过剖面盒的物体)】");
+ int totalCount3, insideCount3, outsideCount3, edgeMissed;
+ CountObjectsInClipBoxByEdgeSampling(out totalCount3, out insideCount3, out outsideCount3, out edgeMissed);
+
+ sb.AppendLine($"总对象数: {totalCount3}");
+ sb.AppendLine($"剖面盒内(棱采样): {insideCount3}");
+ sb.AppendLine($"剖面盒外: {outsideCount3}");
+ sb.AppendLine($"8角点漏检: {edgeMissed}");
+ if (totalCount3 > 0)
+ {
+ double filterRate3 = (double)outsideCount3 / totalCount3 * 100;
+ sb.AppendLine($"过滤率: {filterRate3:F1}%");
+ }
+ if (edgeMissed > 0)
+ {
+ sb.AppendLine($"✅ 发现 {edgeMissed} 个物体被8角点漏检,但被棱采样检出(如穿过剖面盒的柱子)");
+ }
+ sb.AppendLine();
// 4. 测试相交检测
sb.AppendLine("【4. 相交检测测试】");
@@ -1957,9 +1998,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info("剖面盒功能测试完成");
UpdateMainStatus("剖面盒测试完成");
- // 显示结果
- System.Windows.MessageBox.Show(sb.ToString(), "剖面盒功能测试",
- System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
+ // 显示结果(使用可复制的窗口)
+ ShowCopyableMessageBox(sb.ToString(), "剖面盒功能测试");
}
catch (Exception ex)
{
@@ -1971,6 +2011,169 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}, "剖面盒测试");
}
+ ///
+ /// 显示可复制文本的消息框
+ ///
+ private void ShowCopyableMessageBox(string message, string title)
+ {
+ var window = new System.Windows.Window
+ {
+ Title = title,
+ Width = 600,
+ Height = 700,
+ WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen,
+ ResizeMode = System.Windows.ResizeMode.CanResize
+ };
+
+ var grid = new System.Windows.Controls.Grid();
+ grid.RowDefinitions.Add(new System.Windows.Controls.RowDefinition { Height = new System.Windows.GridLength(1, System.Windows.GridUnitType.Star) });
+ grid.RowDefinitions.Add(new System.Windows.Controls.RowDefinition { Height = System.Windows.GridLength.Auto });
+
+ // 可滚动的文本框
+ var scrollViewer = new System.Windows.Controls.ScrollViewer
+ {
+ VerticalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Auto,
+ HorizontalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Auto,
+ Margin = new System.Windows.Thickness(10)
+ };
+
+ // 使用 TextBox 以便选择和复制
+ var textBox = new System.Windows.Controls.TextBox
+ {
+ Text = message,
+ IsReadOnly = true,
+ TextWrapping = System.Windows.TextWrapping.Wrap,
+ AcceptsReturn = true,
+ VerticalScrollBarVisibility = System.Windows.Controls.ScrollBarVisibility.Disabled,
+ BorderThickness = new System.Windows.Thickness(0),
+ Background = System.Windows.Media.Brushes.White,
+ FontFamily = new System.Windows.Media.FontFamily("Consolas"),
+ FontSize = 12
+ };
+
+ scrollViewer.Content = textBox;
+ System.Windows.Controls.Grid.SetRow(scrollViewer, 0);
+ grid.Children.Add(scrollViewer);
+
+ // 确定按钮
+ var buttonPanel = new System.Windows.Controls.StackPanel
+ {
+ Orientation = System.Windows.Controls.Orientation.Horizontal,
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
+ Margin = new System.Windows.Thickness(10)
+ };
+
+ var okButton = new System.Windows.Controls.Button
+ {
+ Content = "确定",
+ Width = 80,
+ Height = 28,
+ Margin = new System.Windows.Thickness(5, 0, 0, 0)
+ };
+ okButton.Click += (s, e) => window.Close();
+
+ // 复制按钮
+ var copyButton = new System.Windows.Controls.Button
+ {
+ Content = "复制全部",
+ Width = 80,
+ Height = 28,
+ Margin = new System.Windows.Thickness(0, 0, 5, 0)
+ };
+ copyButton.Click += (s, e) =>
+ {
+ System.Windows.Clipboard.SetText(message);
+ copyButton.Content = "已复制!";
+ System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
+ {
+ System.Threading.Thread.Sleep(1000);
+ copyButton.Content = "复制全部";
+ }), System.Windows.Threading.DispatcherPriority.Background);
+ };
+
+ buttonPanel.Children.Add(copyButton);
+ buttonPanel.Children.Add(okButton);
+ System.Windows.Controls.Grid.SetRow(buttonPanel, 1);
+ grid.Children.Add(buttonPanel);
+
+ window.Content = grid;
+ window.ShowDialog();
+ }
+
+ ///
+ /// 统计对象数量(使用棱上采样检测)
+ /// 能检测穿过剖面盒的物体(如柱子),而8角点检测可能漏检
+ ///
+ private void CountObjectsInClipBoxByEdgeSampling(out int totalCount, out int insideCount, out int outsideCount, out int edgeMissed)
+ {
+ totalCount = 0;
+ insideCount = 0;
+ outsideCount = 0;
+ edgeMissed = 0;
+
+ try
+ {
+ if (!SectionClipHelper.TryGetCurrentClipBox(out var clipBox))
+ {
+ LogManager.Warning("[剖面盒棱采样统计] 剖面盒未启用");
+ return;
+ }
+
+ var stack = new Stack();
+ foreach (var model in Autodesk.Navisworks.Api.Application.ActiveDocument.Models)
+ {
+ stack.Push(model.RootItem);
+ }
+
+ while (stack.Count > 0)
+ {
+ var item = stack.Pop();
+ totalCount++;
+
+ BoundingBox3D itemBox = item.BoundingBox();
+
+ // 先检查包围盒是否相交(快速排除)
+ if (!clipBox.Intersects(itemBox))
+ {
+ outsideCount++;
+ continue;
+ }
+
+ // 8角点检测
+ bool hasCornerInside = SectionClipHelper.IntersectsByCornerPoints(itemBox, clipBox);
+ // 棱上采样检测(自适应)
+ bool hasEdgeSampleInside = SectionClipHelper.IntersectsByEdgeSampling(itemBox, clipBox, minSamplesPerEdge: 3);
+
+ if (hasEdgeSampleInside)
+ {
+ insideCount++;
+ // 记录被棱采样检出但8角点漏检的物体
+ if (!hasCornerInside)
+ {
+ edgeMissed++;
+ LogManager.Debug($"[棱采样检出] 8角点漏检但被棱采样检出: {item.DisplayName}, 包围盒: X[{itemBox.Min.X:F2},{itemBox.Max.X:F2}], Y[{itemBox.Min.Y:F2},{itemBox.Max.Y:F2}], Z[{itemBox.Min.Z:F2},{itemBox.Max.Z:F2}]");
+ }
+ }
+ else
+ {
+ outsideCount++;
+ }
+
+ // 继续遍历子节点
+ foreach (var child in item.Children)
+ {
+ stack.Push(child);
+ }
+ }
+
+ LogManager.Info($"[剖面盒棱采样统计] 总数: {totalCount}, 盒内(棱采样): {insideCount}, 盒外: {outsideCount}, 8角点漏检: {edgeMissed}");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[剖面盒棱采样统计] 失败: {ex.Message}");
+ }
+ }
+
#endregion
#region 清理资源和IDisposable实现
diff --git a/src/Utils/SectionClipHelper.cs b/src/Utils/SectionClipHelper.cs
index 13c6fd8..d756d9a 100644
--- a/src/Utils/SectionClipHelper.cs
+++ b/src/Utils/SectionClipHelper.cs
@@ -270,6 +270,201 @@ namespace NavisworksTransport.Utils
}
}
+ ///
+ /// 使用采样点(8个角点)检测对象是否真正与剖面盒相交
+ /// 用于排除大包围盒但实际几何体远离剖面盒的对象(如地基)
+ ///
+ public static bool IntersectsByCornerPoints(BoundingBox3D itemBox, BoundingBox3D clipBox)
+ {
+ // 获取对象包围盒的8个角点
+ var corners = new Point3D[]
+ {
+ new Point3D(itemBox.Min.X, itemBox.Min.Y, itemBox.Min.Z),
+ new Point3D(itemBox.Min.X, itemBox.Min.Y, itemBox.Max.Z),
+ new Point3D(itemBox.Min.X, itemBox.Max.Y, itemBox.Min.Z),
+ new Point3D(itemBox.Min.X, itemBox.Max.Y, itemBox.Max.Z),
+ new Point3D(itemBox.Max.X, itemBox.Min.Y, itemBox.Min.Z),
+ new Point3D(itemBox.Max.X, itemBox.Min.Y, itemBox.Max.Z),
+ new Point3D(itemBox.Max.X, itemBox.Max.Y, itemBox.Min.Z),
+ new Point3D(itemBox.Max.X, itemBox.Max.Y, itemBox.Max.Z)
+ };
+
+ // 检查是否有任何角点在剖面盒内
+ foreach (var corner in corners)
+ {
+ if (clipBox.Contains(corner))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 使用棱上自适应采样检测对象是否与剖面盒相交
+ /// 根据物体大小和剖面盒尺寸动态计算采样点数,确保长物体(如管道)不会漏检
+ /// 采样间距小于剖面盒最小边长,确保任何穿过剖面盒的物体都能被检测到
+ ///
+ /// 对象包围盒
+ /// 剖面盒
+ /// 每条棱的最小采样点数(默认3个)
+ /// 是否有采样点在剖面盒内
+ public static bool IntersectsByEdgeSampling(BoundingBox3D itemBox, BoundingBox3D clipBox, int minSamplesPerEdge = 3)
+ {
+ // 计算剖面盒的最小边长(作为采样间距上限)
+ double clipBoxMinSize = Math.Min(
+ Math.Min(clipBox.Max.X - clipBox.Min.X, clipBox.Max.Y - clipBox.Min.Y),
+ clipBox.Max.Z - clipBox.Min.Z
+ );
+
+ // 计算物体包围盒的三边长度
+ double itemSizeX = itemBox.Max.X - itemBox.Min.X;
+ double itemSizeY = itemBox.Max.Y - itemBox.Min.Y;
+ double itemSizeZ = itemBox.Max.Z - itemBox.Min.Z;
+
+ // 根据边长动态计算每条棱的采样点数
+ // 采样间距 = 边长 / (采样点数 - 1),确保间距 < 剖面盒最小边长
+ int GetSampleCount(double edgeLength)
+ {
+ if (edgeLength <= 0) return minSamplesPerEdge;
+ // 需要的采样点数 = 边长 / 剖面盒尺寸 + 1,至少minSamplesPerEdge个
+ int count = (int)Math.Ceiling(edgeLength / clipBoxMinSize) + 1;
+ return Math.Max(count, minSamplesPerEdge);
+ }
+
+ int samplesX = GetSampleCount(itemSizeX);
+ int samplesY = GetSampleCount(itemSizeY);
+ int samplesZ = GetSampleCount(itemSizeZ);
+
+ // 12条棱的端点定义(索引对应8个角点)和采样点数
+ // 0: (0,0,0) 1: (0,0,1) 2: (0,1,0) 3: (0,1,1)
+ // 4: (1,0,0) 5: (1,0,1) 6: (1,1,0) 7: (1,1,1)
+ var edges = new (int start, int end, int samples)[]
+ {
+ // X方向棱(4条),用samplesX
+ (0, 4, samplesX), (1, 5, samplesX), (2, 6, samplesX), (3, 7, samplesX),
+ // Y方向棱(4条),用samplesY
+ (0, 2, samplesY), (1, 3, samplesY), (4, 6, samplesY), (5, 7, samplesY),
+ // Z方向棱(4条),用samplesZ
+ (0, 1, samplesZ), (2, 3, samplesZ), (4, 5, samplesZ), (6, 7, samplesZ)
+ };
+
+ // 8个角点的坐标
+ var cornerPoints = new Point3D[]
+ {
+ new Point3D(itemBox.Min.X, itemBox.Min.Y, itemBox.Min.Z),
+ new Point3D(itemBox.Min.X, itemBox.Min.Y, itemBox.Max.Z),
+ new Point3D(itemBox.Min.X, itemBox.Max.Y, itemBox.Min.Z),
+ new Point3D(itemBox.Min.X, itemBox.Max.Y, itemBox.Max.Z),
+ new Point3D(itemBox.Max.X, itemBox.Min.Y, itemBox.Min.Z),
+ new Point3D(itemBox.Max.X, itemBox.Min.Y, itemBox.Max.Z),
+ new Point3D(itemBox.Max.X, itemBox.Max.Y, itemBox.Min.Z),
+ new Point3D(itemBox.Max.X, itemBox.Max.Y, itemBox.Max.Z)
+ };
+
+ // 在每条棱上采样
+ foreach (var edge in edges)
+ {
+ var start = cornerPoints[edge.start];
+ var end = cornerPoints[edge.end];
+ int samples = edge.samples;
+
+ for (int i = 0; i < samples; i++)
+ {
+ double t = (double)i / (samples - 1); // 0.0 到 1.0
+ var samplePoint = new Point3D(
+ start.X + t * (end.X - start.X),
+ start.Y + t * (end.Y - start.Y),
+ start.Z + t * (end.Z - start.Z)
+ );
+
+ if (clipBox.Contains(samplePoint))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 统计对象数量(使用采样点检测)
+ /// 与 CountObjectsInClipBox 的区别:使用8个角点检测,排除大包围盒假阳性
+ ///
+ public static void CountObjectsInClipBoxByCorners(out int totalCount, out int insideCount, out int outsideCount, out int largeBoxFiltered, List debugDetails = null)
+ {
+ totalCount = 0;
+ insideCount = 0;
+ outsideCount = 0;
+ largeBoxFiltered = 0;
+
+ try
+ {
+ if (!TryGetCurrentClipBox(out var clipBox))
+ {
+ LogManager.Warning("[剖面盒角点统计] 剖面盒未启用");
+ return;
+ }
+
+ var stack = new Stack();
+ foreach (var model in Application.ActiveDocument.Models)
+ {
+ stack.Push(model.RootItem);
+ }
+
+ while (stack.Count > 0)
+ {
+ var item = stack.Pop();
+ totalCount++;
+
+ BoundingBox3D itemBox = item.BoundingBox();
+
+ // 先检查包围盒是否相交(快速排除)
+ if (!clipBox.Intersects(itemBox))
+ {
+ outsideCount++;
+ continue; // 跳过子节点(剪枝)
+ }
+
+ // 相交:再用角点检测确认
+ bool hasCornerInside = IntersectsByCornerPoints(itemBox, clipBox);
+
+ if (hasCornerInside)
+ {
+ insideCount++;
+ }
+ else
+ {
+ outsideCount++;
+ largeBoxFiltered++;
+
+ // 记录被过滤的大对象
+ if (item.HasGeometry)
+ {
+ LogManager.Debug($"[剖面盒角点过滤] 大包围盒但无角点在盒内: {item.DisplayName}, 包围盒: X[{itemBox.Min.X:F2},{itemBox.Max.X:F2}], Y[{itemBox.Min.Y:F2},{itemBox.Max.Y:F2}], Z[{itemBox.Min.Z:F2},{itemBox.Max.Z:F2}]");
+ }
+
+ // 注意:这里继续遍历子节点,因为父对象可能只是外壳,子对象可能在盒内
+ }
+
+ // 继续遍历子节点
+ foreach (var child in item.Children)
+ {
+ stack.Push(child);
+ }
+ }
+
+ LogManager.Info($"[剖面盒角点统计] 总数: {totalCount}, 盒内(角点): {insideCount}, 盒外: {outsideCount}, 大包围盒过滤: {largeBoxFiltered}, " +
+ $"过滤率: {(outsideCount * 100.0 / totalCount):F1}%");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[剖面盒角点统计] 失败: {ex.Message}");
+ }
+ }
+
///
/// 测试包围盒相交检测(用于验证)
///