进行剖面盒的bug修复

This commit is contained in:
tian 2026-02-12 17:04:59 +08:00
parent 73c26601fd
commit 9caf3c9461
4 changed files with 430 additions and 15 deletions

View File

@ -1963,7 +1963,8 @@ namespace NavisworksTransport.Core.Animation
/// <summary>
/// 安全获取ModelItem的DisplayName避免访问已释放对象的警告
/// 安全获取ModelItem的显示名称避免访问已释放对象的警告
/// 优先使用ClassName其次DisplayName
/// </summary>
/// <param name="item">ModelItem对象</param>
/// <returns>安全的显示名称</returns>
@ -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
{

View File

@ -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

View File

@ -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
}, "剖面盒测试");
}
/// <summary>
/// 显示可复制文本的消息框
/// </summary>
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();
}
/// <summary>
/// 统计对象数量(使用棱上采样检测)
/// 能检测穿过剖面盒的物体如柱子而8角点检测可能漏检
/// </summary>
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<ModelItem>();
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实现

View File

@ -270,6 +270,201 @@ namespace NavisworksTransport.Utils
}
}
/// <summary>
/// 使用采样点8个角点检测对象是否真正与剖面盒相交
/// 用于排除大包围盒但实际几何体远离剖面盒的对象(如地基)
/// </summary>
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;
}
/// <summary>
/// 使用棱上自适应采样检测对象是否与剖面盒相交
/// 根据物体大小和剖面盒尺寸动态计算采样点数,确保长物体(如管道)不会漏检
/// 采样间距小于剖面盒最小边长,确保任何穿过剖面盒的物体都能被检测到
/// </summary>
/// <param name="itemBox">对象包围盒</param>
/// <param name="clipBox">剖面盒</param>
/// <param name="minSamplesPerEdge">每条棱的最小采样点数默认3个</param>
/// <returns>是否有采样点在剖面盒内</returns>
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;
}
/// <summary>
/// 统计对象数量(使用采样点检测)
/// 与 CountObjectsInClipBox 的区别使用8个角点检测排除大包围盒假阳性
/// </summary>
public static void CountObjectsInClipBoxByCorners(out int totalCount, out int insideCount, out int outsideCount, out int largeBoxFiltered, List<string> debugDetails = null)
{
totalCount = 0;
insideCount = 0;
outsideCount = 0;
largeBoxFiltered = 0;
try
{
if (!TryGetCurrentClipBox(out var clipBox))
{
LogManager.Warning("[剖面盒角点统计] 剖面盒未启用");
return;
}
var stack = new Stack<ModelItem>();
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}");
}
}
/// <summary>
/// 测试包围盒相交检测(用于验证)
/// </summary>