优化剖面盒导出性能

This commit is contained in:
tian 2026-03-11 23:17:58 +08:00
parent 3b801c6bd4
commit 8b4afcfe04
5 changed files with 295 additions and 128 deletions

View File

@ -737,6 +737,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand SaveSelectedItemsCommand { get; private set; }
public ICommand CancelOperationCommand { get; private set; }
public ICommand TestExportToNwdCommand { get; private set; }
public ICommand ExportSectionBoxCommand { get; private set; }
// 楼层属性相关命令
public ICommand RefreshSelectionCommand { get; private set; }
@ -894,6 +895,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
async () => await SaveSelectedItemsAsync(),
() => HasSelectedItems);
ExportSectionBoxCommand = new RelayCommand(
async () => await ExportSectionBoxAsync(),
() => IsNotProcessing);
CancelOperationCommand = new RelayCommand(CancelOperation, () => IsProcessing);
TestExportToNwdCommand = new RelayCommand(
@ -1792,27 +1797,42 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// </summary>
private async Task SaveSelectedItemsAsync()
{
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (document?.CurrentSelection?.SelectedItems == null || document.CurrentSelection.SelectedItems.Count == 0)
{
MessageBox.Show("请先选择要保存的项目", "保存提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var items = document.CurrentSelection.SelectedItems.ToList();
string defaultFileName = GenerateDefaultFileNameForMultipleItems(document, items);
await ExportItemsToNwdAsync(items, "保存当前选择项", defaultFileName, true);
}
/// <summary>
/// 通用导出方法 - 导出指定对象列表到NWD
/// </summary>
/// <param name="items">要导出的对象列表</param>
/// <param name="dialogTitle">对话框标题</param>
/// <param name="defaultFileName">默认文件名</param>
/// <param name="restoreSelection">是否恢复原始选择</param>
private async Task ExportItemsToNwdAsync(List<ModelItem> items, string dialogTitle, string defaultFileName, bool restoreSelection = false)
{
if (items == null || items.Count == 0)
{
MessageBox.Show("没有要导出的对象", "导出提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
try
{
LogManager.Info("[LayerManagementViewModel] 开始保存当前选择项");
LogManager.Info($"[LayerManagementViewModel] 开始导出 {items.Count} 个对象");
IsProcessing = true;
CurrentOperationText = "准备导出...";
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (document?.Models?.Count == 0 || document.CurrentSelection.SelectedItems.Count == 0)
{
LogManager.Warning("[LayerManagementViewModel] 没有活动文档或未选择节点");
MessageBox.Show("请先选择要保存的项目", "保存提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 保存当前选择状态
var originalSelection = new List<ModelItem>(document.CurrentSelection.SelectedItems);
LogManager.Info($"[LayerManagementViewModel] 用户选中了 {originalSelection.Count} 个节点");
// 生成默认文件名
string defaultFileName = GenerateDefaultFileNameForMultipleItems(document, originalSelection);
var originalSelection = restoreSelection ? document.CurrentSelection.SelectedItems.ToList() : null;
// 获取保存路径
string saveFilePath = null;
@ -1820,7 +1840,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
var saveDialog = new Microsoft.Win32.SaveFileDialog
{
Title = "保存当前选择项",
Title = dialogTitle,
Filter = "Navisworks文件 (*.nwd)|*.nwd",
DefaultExt = "nwd",
FileName = defaultFileName
@ -1840,22 +1860,36 @@ namespace NavisworksTransport.UI.WPF.ViewModels
bool exportResult = false;
string errorMessage = "";
// 在主线程执行导出
// 在主线程执行导出显示Navisworks系统进度条
await Task.Run(() =>
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
// 设置等待光标
System.Windows.Input.Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
// 开始Navisworks系统进度条
var progress = Autodesk.Navisworks.Api.Application.BeginProgress(
dialogTitle,
$"正在导出 {items.Count} 个对象到 NWD 文件...");
try
{
// 收集要导出的项目 (仅根节点)
CurrentOperationText = $"正在导出 {items.Count} 个对象...";
progress.Update(0.2);
// 收集要导出的项目
var itemsToExport = new ModelItemCollection();
itemsToExport.AddRange(originalSelection);
itemsToExport.AddRange(items);
LogManager.Info($"[LayerManagementViewModel] 准备导出 {itemsToExport.Count} 个节点");
progress.Update(0.4);
// 在隔离模式下执行导出,自动恢复可见性
VisibilityHelper.ExecuteInIsolation(itemsToExport, () =>
{
progress.Update(0.6);
// 导出选项
var exportOptions = new Autodesk.Navisworks.Api.NwdExportOptions
{
@ -1864,10 +1898,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels
PreventObjectPropertyExport = PreventObjectPropertyExport
};
progress.Update(0.8);
document.ExportToNwd(saveFilePath, exportOptions);
exportResult = true;
LogManager.Info("[LayerManagementViewModel] ExportToNwd API调用完成");
});
progress.Update(1.0);
}
catch (Exception ex)
{
@ -1875,22 +1912,32 @@ namespace NavisworksTransport.UI.WPF.ViewModels
errorMessage = ex.Message;
exportResult = false;
}
finally
{
// 确保进度条被关闭
Autodesk.Navisworks.Api.Application.EndProgress();
// 恢复光标
System.Windows.Input.Mouse.OverrideCursor = null;
}
});
});
// 恢复原始选择 (需要在主线程)
System.Windows.Application.Current.Dispatcher.Invoke(() =>
// 恢复原始选择(如果需要)
if (restoreSelection && originalSelection != null)
{
try
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
document.CurrentSelection.Clear();
document.CurrentSelection.AddRange(originalSelection);
}
catch (Exception ex)
{
LogManager.Warning($"[LayerManagementViewModel] 恢复选择失败: {ex.Message}");
}
});
try
{
document.CurrentSelection.Clear();
document.CurrentSelection.AddRange(originalSelection);
}
catch (Exception ex)
{
LogManager.Warning($"[LayerManagementViewModel] 恢复选择失败: {ex.Message}");
}
});
}
// 显示结果
if (exportResult)
@ -1899,23 +1946,163 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
var fileInfo = new FileInfo(saveFilePath);
MessageBox.Show(
$"保存成功!\n\n文件: {saveFilePath}\n大小: {fileInfo.Length / 1024} KB",
"保存结果", MessageBoxButton.OK, MessageBoxImage.Information);
$"导出成功!\n\n文件: {saveFilePath}\n大小: {fileInfo.Length / 1024} KB",
"导出结果", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
else
{
MessageBox.Show($"保存失败: {errorMessage}", "保存错误", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show($"导出失败: {errorMessage}", "导出错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 保存选中项目过程异常: {ex.Message}", ex);
MessageBox.Show($"保存过程异常: {ex.Message}", "保存错误", MessageBoxButton.OK, MessageBoxImage.Error);
LogManager.Error($"[LayerManagementViewModel] 导出过程异常: {ex.Message}", ex);
MessageBox.Show($"导出过程异常: {ex.Message}", "导出错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsProcessing = false;
CurrentOperationText = "";
}
}
/// <summary>
/// 导出剖面盒内的对象
/// </summary>
private async Task ExportSectionBoxAsync()
{
try
{
IsProcessing = true;
CurrentOperationText = "检查剖面盒...";
var document = NavisApplication.ActiveDocument;
if (document == null)
{
MessageBox.Show("没有活动的文档!请先打开一个模型。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
// 检查剖面盒
var viewpoint = document.CurrentViewpoint.Value;
var clipPlanes = viewpoint.ClipPlanes;
if (clipPlanes == null || !clipPlanes.Enabled)
{
MessageBox.Show(
"当前没有活动的剖面盒!\n请先使用 Navisworks 的剖面工具创建剖面盒。",
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
if (clipPlanes.Mode != ClipPlaneSetMode.Box)
{
MessageBox.Show(
"当前是平面裁剪模式,请切换为剖面盒模式!\n在 Navisworks 剖面工具中选择'Box'模式。",
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
// 获取剖面盒边界
var box = clipPlanes.Box;
if (box == null)
{
MessageBox.Show("无法获取剖面盒边界!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
var sectionBoxBounds = new BoundingBox3D(
new Point3D(box.Min.X, box.Min.Y, box.Min.Z),
new Point3D(box.Max.X, box.Max.Y, box.Max.Z)
);
// 异步获取剖面盒内的对象
CurrentOperationText = "正在查找剖面盒内的对象...";
List<ModelItem> objectsInSectionBox = null;
await Task.Run(() =>
{
objectsInSectionBox = GetObjectsInSectionBox(document, sectionBoxBounds);
});
if (objectsInSectionBox.Count == 0)
{
MessageBox.Show("剖面盒内没有找到对象!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
LogManager.Info($"[LayerManagementViewModel] 剖面盒内找到 {objectsInSectionBox.Count} 个对象");
// 生成默认文件名
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string defaultFileName = $"SectionBox_Export_{timestamp}";
// 调用通用导出方法
await ExportItemsToNwdAsync(objectsInSectionBox, "导出剖面盒", defaultFileName, false);
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 导出剖面盒异常: {ex.Message}", ex);
MessageBox.Show($"导出剖面盒异常: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsProcessing = false;
CurrentOperationText = "";
}
}
/// <summary>
/// 获取剖面盒内的所有对象
/// </summary>
private List<ModelItem> GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox)
{
var result = new List<ModelItem>();
foreach (var model in document.Models)
{
var rootItem = model.RootItem;
if (rootItem != null)
{
TraverseModelItem(rootItem, sectionBox, result);
}
}
return result;
}
/// <summary>
/// 递归遍历模型项 - 剪枝策略
/// </summary>
private void TraverseModelItem(ModelItem item, BoundingBox3D sectionBox, List<ModelItem> result)
{
if (item == null) return;
if (item.IsHidden) return;
// 如果有几何体信息,检查并添加
if (item.HasGeometry)
{
var bbox = item.BoundingBox();
// 检查是否有体积
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 && BoundingBoxGeometryUtils.BoundingBoxesIntersect(bbox, sectionBox))
{
result.Add(item);
}
return;
}
// 没有几何体,继续遍历子节点
foreach (var child in item.Children)
{
TraverseModelItem(child, sectionBox, result);
}
}

View File

@ -174,7 +174,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand TestVoxelPathFindingCommand { get; private set; }
public ICommand ReadTransformTestCommand { get; private set; }
public ICommand CoordinateSystemExplorerCommand { get; private set; }
public ICommand ExportSectionBoxCommand { get; private set; }
// 坐标系设置
public ObservableCollection<string> CoordinateSystemOptions { get; private set; }
@ -311,7 +311,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
TestVoxelPathFindingCommand = new RelayCommand(() => ExecuteTestVoxelPathFinding());
ReadTransformTestCommand = new RelayCommand(() => ExecuteReadTransformTest());
CoordinateSystemExplorerCommand = new RelayCommand(() => ExecuteCoordinateSystemExplorer());
ExportSectionBoxCommand = new RelayCommand(() => ExecuteExportSectionBox());
// 初始化坐标系选项
CoordinateSystemOptions = new ObservableCollection<string> { "AutoDetect", "ZUp", "YUp" };
@ -1658,68 +1658,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}, "坐标系检测");
}
/// <summary>
/// 执行导出剖面盒功能
/// </summary>
private void ExecuteExportSectionBox()
{
SafeExecute(() =>
{
try
{
UpdateMainStatus("正在导出剖面盒信息...");
LogManager.Info("开始导出剖面盒信息");
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
System.Windows.MessageBox.Show(
"没有活动的文档!请先打开一个模型。",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error);
UpdateMainStatus("导出剖面盒失败:无活动文档");
return;
}
// 使用导出功能类
var exporter = new NavisworksTransport.Core.SectionBoxExporter();
string filePath = exporter.ExportToJson(doc);
if (!string.IsNullOrEmpty(filePath))
{
var result = System.Windows.MessageBox.Show(
$"剖面盒信息已导出到:\n{filePath}\n\n是否打开文件所在目录",
"导出成功",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Information);
if (result == System.Windows.MessageBoxResult.Yes)
{
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{filePath}\"");
}
UpdateMainStatus($"剖面盒信息已导出: {filePath}");
LogManager.Info($"剖面盒信息导出成功: {filePath}");
}
else
{
UpdateMainStatus("导出剖面盒被取消或失败");
}
}
catch (Exception ex)
{
LogManager.Error($"导出剖面盒异常: {ex.Message}", ex);
System.Windows.MessageBox.Show(
$"导出剖面盒出现异常:\n{ex.Message}",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error);
UpdateMainStatus($"导出剖面盒异常: {ex.Message}");
}
}, "导出剖面盒");
}
#endregion
#region

View File

@ -325,6 +325,28 @@ NavisworksTransport 分层管理页签视图 - 重构优化版本
</StackPanel>
</StackPanel>
</Border>
<!-- 区域4: 剖面盒导出 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,0,0,15" Padding="12">
<StackPanel>
<Label Content="剖面盒导出" Style="{StaticResource SectionHeaderStyle}"/>
<!-- 说明文字 -->
<TextBlock Text="导出剖面盒内的对象为NWD或JSON格式"
Style="{StaticResource StatusTextStyle}"
Margin="0,5,0,10"/>
<!-- 导出按钮 -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0,5,0,0">
<Button Content="导出剖面盒"
Style="{StaticResource ActionButtonStyle}"
Command="{Binding ExportSectionBoxCommand}"
IsEnabled="{Binding IsNotProcessing}"/>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>

View File

@ -226,11 +226,6 @@ NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Nav
Style="{StaticResource SecondaryButtonStyle}"
Command="{Binding DiagnosticCommand}"
ToolTip="检查运行环境和线程状态"/>
<Button Content="导出剖面盒"
Command="{Binding ExportSectionBoxCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="导出当前剖面盒内的对象包围盒信息到JSON文件"/>
</StackPanel>
</StackPanel>
</Border>

View File

@ -32,7 +32,7 @@ namespace NavisworksTransport
}
/// <summary>
/// 仅显示指定的模型项集合,隐藏其他所有项
/// 仅显示指定的模型项集合,隐藏其他所有项(高性能版本)
/// </summary>
/// <param name="itemsToShow">要显示的模型项集合</param>
/// <returns>操作是否成功</returns>
@ -55,41 +55,66 @@ namespace NavisworksTransport
return false;
}
LogManager.Info($"[VisibilityManager] 开始隔离显示,共 {itemsToShow.Count} 个对象");
// 1. 重置所有隐藏状态
document.Models.ResetAllHidden();
// 2. 创建可见项集合(要保证从根节点开始的路径完整)
ModelItemCollection visible = new ModelItemCollection();
// 2. 使用 HashSet 收集需要显示的所有节点(包括祖先)
var visibleSet = new HashSet<ModelItem>();
foreach (ModelItem item in itemsToShow)
{
// 添加祖先路径
if (item.AncestorsAndSelf != null)
visible.AddRange(item.AncestorsAndSelf);
}
// 3. 创建候选隐藏集合 - 收集所有可见项的兄弟节点
ModelItemCollection hidden = new ModelItemCollection();
foreach (ModelItem toShow in visible)
{
if (toShow.Parent != null)
var ancestors = item.AncestorsAndSelf;
if (ancestors != null)
{
// 添加父节点的所有子节点
hidden.AddRange(toShow.Parent.Children);
foreach (var ancestor in ancestors)
{
visibleSet.Add(ancestor);
}
}
}
// 4. 从隐藏集合中移除可见项
foreach (ModelItem toShow in visible)
LogManager.Info($"[VisibilityManager] 收集祖先完成visibleSet={visibleSet.Count}");
// 3. 收集需要隐藏的节点(兄弟节点)
// 优化先收集唯一的父节点避免重复遍历同一个Parent的Children
var uniqueParents = new HashSet<ModelItem>();
foreach (ModelItem visibleItem in visibleSet)
{
hidden.Remove(toShow);
if (visibleItem.Parent != null)
{
uniqueParents.Add(visibleItem.Parent);
}
}
// 5. 执行隐藏操作
if (hidden.Count > 0)
LogManager.Info($"[VisibilityManager] 收集唯一父节点完成uniqueParents={uniqueParents.Count}");
// 然后只遍历唯一的父节点的Children
var hiddenSet = new HashSet<ModelItem>();
foreach (var parent in uniqueParents)
{
document.Models.SetHidden(hidden, true);
foreach (var sibling in parent.Children)
{
hiddenSet.Add(sibling);
}
}
LogManager.Info($"[VisibilityManager] 收集兄弟完成hiddenSet={hiddenSet.Count}");
// 4. 从隐藏集合中移除可见项HashSet.Remove 是 O(1)
hiddenSet.ExceptWith(visibleSet);
LogManager.Info($"[VisibilityManager] 排除可见项后hiddenSet={hiddenSet.Count}");
// 5. 执行隐藏操作
if (hiddenSet.Count > 0)
{
var hiddenCollection = new ModelItemCollection();
foreach (var item in hiddenSet)
{
hiddenCollection.Add(item);
}
document.Models.SetHidden(hiddenCollection, true);
}
stopwatch.Stop();